Migrate to actix 4.0 and tokio 1

This commit is contained in:
silverpill 2022-04-08 18:52:13 +00:00
parent ed68b728be
commit 30bd3d6a37
11 changed files with 786 additions and 1085 deletions

1732
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,18 +5,18 @@ description = "Mitra backend"
license = "AGPL-3.0" license = "AGPL-3.0"
edition = "2018" edition = "2018"
rust-version = "1.53" rust-version = "1.54"
publish = false publish = false
default-run = "mitra" default-run = "mitra"
[dependencies] [dependencies]
# Used to handle incoming HTTP requests # Used to handle incoming HTTP requests
actix-cors = "0.5.4" actix-cors = "0.6.1"
actix-files = "0.5.0" actix-files = "0.6.0"
actix-web = "3.3.3" actix-web = "4.0.1"
actix-web-httpauth = "0.5.1" actix-web-httpauth = "0.6.0"
# Used for managing async tasks # Used for managing async tasks
actix-rt = "1.1.1" actix-rt = "2.7.0"
# Used for HTML sanitization # Used for HTML sanitization
ammonia = "3.1.2" ammonia = "3.1.2"
# Used for working with RSA keys, HTTP signatures and file uploads # Used for working with RSA keys, HTTP signatures and file uploads
@ -26,9 +26,9 @@ chrono = { version = "0.4.19", features = ["serde"] }
# Used to build admin CLI tool # Used to build admin CLI tool
# Versions greater than beta.2 require Rust 1.54 # Versions greater than beta.2 require Rust 1.54
clap = { version = "3.0.0-beta.2", default-features = false, features = ["std", "derive"] } clap = { version = "3.0.0-beta.2", default-features = false, features = ["std", "derive"] }
# Used for pooling database connections (compatible with tokio 0.2) # Used for pooling database connections
deadpool = "0.5.2" deadpool = "0.9.2"
deadpool-postgres = { version = "0.5.6", default-features = false } deadpool-postgres = { version = "0.10.2", default-features = false }
# Used to read .env files # Used to read .env files
dotenv = "0.15.0" dotenv = "0.15.0"
# Used to work with hexadecimal strings # Used to work with hexadecimal strings
@ -46,9 +46,9 @@ regex = "1.5.4"
# Used to generate random numbers # Used to generate random numbers
rand = "0.8.4" rand = "0.8.4"
# Used for managing database migrations # Used for managing database migrations
refinery = { version = "0.4.0", features = ["tokio-postgres"] } refinery = { version = "0.8.4", features = ["tokio-postgres"] }
# Used for making async HTTP requests # Used for making async HTTP requests
reqwest = { version = "0.10.10", features = ["json"] } reqwest = { version = "0.11.10", features = ["json", "multipart"] }
# Used for working with RSA keys # Used for working with RSA keys
rsa = "0.5.0" rsa = "0.5.0"
pem = "1.0.2" pem = "1.0.2"
@ -57,8 +57,7 @@ rust-argon2 = "0.8.3"
# Used for working with ethereum keys # Used for working with ethereum keys
secp256k1 = { version = "0.20.3", features = ["rand", "rand-std"] } secp256k1 = { version = "0.20.3", features = ["rand", "rand-std"] }
# Used for serialization/deserialization # Used for serialization/deserialization
# https://github.com/rust-db/refinery/issues/160 serde = { version = "1.0.136", features = ["derive"] }
serde = { version = "=1.0.117", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
# Used to parse config file # Used to parse config file
serde_yaml = "0.8.17" serde_yaml = "0.8.17"
@ -68,15 +67,15 @@ sha2 = "0.9.5"
siwe = { git = "https://github.com/silverpill/siwe-rs", rev = "6589eb6fdcffbfb9ee2880906014f5cf71f0f441" } siwe = { git = "https://github.com/silverpill/siwe-rs", rev = "6589eb6fdcffbfb9ee2880906014f5cf71f0f441" }
# Used for creating error types # Used for creating error types
thiserror = "1.0.24" thiserror = "1.0.24"
# Async runtime ( required for #[tokio::main] ) # Async runtime
tokio = { version = "0.2.25", features = ["macros"] } tokio = { version = "1.17.0", features = ["macros"] }
# Used for working with Postgresql database (compatible with tokio 0.2) # Used for working with Postgresql database
tokio-postgres = { version = "0.5.5", features = ["with-chrono-0_4", "with-uuid-0_8", "with-serde_json-1"] } tokio-postgres = { version = "0.7.5", features = ["with-chrono-0_4", "with-uuid-0_8", "with-serde_json-1"] }
postgres-types = { version = "0.1.2", features = ["derive", "with-chrono-0_4", "with-uuid-0_8", "with-serde_json-1"] } postgres-types = { version = "0.2.2", features = ["derive", "with-chrono-0_4", "with-uuid-0_8", "with-serde_json-1"] }
postgres-protocol = "0.5.3" postgres-protocol = "0.6.1"
# Used to construct PostgreSQL queries # Used to construct PostgreSQL queries
postgres_query = "0.3.3" postgres_query = { git = "https://github.com/nolanderc/rust-postgres-query", rev = "b4422051c8a31fbba4a35f88004c1cefb1878dd5" }
postgres_query_macro = "0.3.1" postgres_query_macro = { git = "https://github.com/nolanderc/rust-postgres-query", rev = "b4422051c8a31fbba4a35f88004c1cefb1878dd5" }
# Used to work with URLs # Used to work with URLs
url = "2.2.2" url = "2.2.2"
# Used to generate lexicographically sortable IDs # Used to generate lexicographically sortable IDs
@ -84,7 +83,9 @@ ulid = { version = "0.4.1", features = ["uuid"] }
# Used to work with UUIDs # Used to work with UUIDs
uuid = { version = "0.8.2", features = ["serde", "v4"] } uuid = { version = "0.8.2", features = ["serde", "v4"] }
# Used to query ethereum node # Used to query ethereum node
web3 = { version = "0.15.0", default-features = false, features = ["http", "http-tls", "signing"] } web3 = { version = "0.16.0", default-features = false, features = ["http", "http-tls", "signing"] }
# Dependency of web3; version 0.2.2 requires edition 2021
impl-trait-for-tuples = "=0.2.1"
[dev-dependencies] [dev-dependencies]
serial_test = "0.5.1" serial_test = "0.5.1"

View file

@ -21,7 +21,7 @@ Demo instance: https://mitra.social/ (invite-only)
## Requirements ## Requirements
- Rust 1.53+ - Rust 1.54+
- PostgreSQL 10.2+ - PostgreSQL 10.2+
- IPFS node (optional, see [guide](./docs/ipfs.md)) - IPFS node (optional, see [guide](./docs/ipfs.md))
- Ethereum node (optional) - Ethereum node (optional)

View file

@ -1,7 +1,7 @@
use actix_web::{ use actix_web::{
get, post, web, get, post, web,
HttpRequest, HttpResponse, Scope, HttpRequest, HttpResponse, Scope,
http::HeaderMap, http::header::HeaderMap,
}; };
use serde::Deserialize; use serde::Deserialize;
use uuid::Uuid; use uuid::Uuid;
@ -84,7 +84,7 @@ async fn actor_view(
if !is_activitypub_request(request.headers()) { if !is_activitypub_request(request.headers()) {
let page_url = get_profile_page_url(&config.instance_url(), &user.id); let page_url = get_profile_page_url(&config.instance_url(), &user.id);
let response = HttpResponse::Found() let response = HttpResponse::Found()
.header("Location", page_url) .append_header(("Location", page_url))
.finish(); .finish();
return Ok(response); return Ok(response);
}; };
@ -293,7 +293,7 @@ pub async fn object_view(
if !is_activitypub_request(request.headers()) { if !is_activitypub_request(request.headers()) {
let page_url = get_post_page_url(&config.instance_url(), &post.id); let page_url = get_post_page_url(&config.instance_url(), &post.id);
let response = HttpResponse::Found() let response = HttpResponse::Found()
.header("Location", page_url) .append_header(("Location", page_url))
.finish(); .finish();
return Ok(response); return Ok(response);
}; };
@ -321,7 +321,10 @@ pub async fn object_view(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_web::http::{header, HeaderMap, HeaderValue}; use actix_web::http::{
header,
header::{HeaderMap, HeaderValue},
};
use super::*; use super::*;
#[test] #[test]

View file

@ -1,4 +1,6 @@
use tokio_postgres::config::{Config as DbConfig}; use tokio_postgres::config::{Config as DbConfig};
use tokio_postgres::error::{Error as PgError, SqlState};
use crate::errors::DatabaseError;
pub mod int_enum; pub mod int_enum;
pub mod migrate; pub mod migrate;
@ -21,19 +23,15 @@ pub async fn create_database_client(db_config: &DbConfig) -> tokio_postgres::Cli
} }
pub fn create_pool(database_url: &str) -> Pool { pub fn create_pool(database_url: &str) -> Pool {
deadpool_postgres::Pool::new( let manager = deadpool_postgres::Manager::new(
deadpool_postgres::Manager::new( database_url.parse().expect("invalid database URL"),
database_url.parse().expect("invalid database URL"), tokio_postgres::NoTls,
tokio_postgres::NoTls, );
), // https://wiki.postgresql.org/wiki/Number_Of_Database_Connections
// https://wiki.postgresql.org/wiki/Number_Of_Database_Connections let pool_size = num_cpus::get() * 2;
num_cpus::get() * 2, Pool::builder(manager).max_size(pool_size).build().unwrap()
)
} }
use tokio_postgres::error::{Error as PgError, SqlState};
use crate::errors::DatabaseError;
pub async fn get_database_client(pool: &Pool) pub async fn get_database_client(pool: &Pool)
-> Result<deadpool_postgres::Client, DatabaseError> -> Result<deadpool_postgres::Client, DatabaseError>
{ {

View file

@ -1,7 +1,6 @@
use actix_web::{ use actix_web::{
dev::HttpResponseBuilder,
http::StatusCode, http::StatusCode,
HttpResponse, HttpResponse, HttpResponseBuilder,
error::ResponseError, error::ResponseError,
}; };
use serde::Serialize; use serde::Serialize;

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use actix_web::{ use actix_web::{
HttpRequest, HttpRequest,
http::{HeaderMap, Method, Uri}, http::{Method, Uri, header::HeaderMap},
}; };
use regex::Regex; use regex::Regex;
use tokio_postgres::GenericClient; use tokio_postgres::GenericClient;
@ -154,7 +154,11 @@ pub async fn verify_http_signature(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_web::http::{header, HeaderMap, HeaderName, HeaderValue, Uri}; use actix_web::http::{
header,
header::{HeaderMap, HeaderName, HeaderValue},
Uri,
};
use super::*; use super::*;
#[test] #[test]

View file

@ -77,10 +77,10 @@ async fn main() -> std::io::Result<()> {
.wrap(ActixLogger::new("%r : %s : %{r}a")) .wrap(ActixLogger::new("%r : %s : %{r}a"))
.wrap(cors_config) .wrap(cors_config)
.wrap(create_auth_error_handler()) .wrap(create_auth_error_handler())
.data(web::PayloadConfig::default().limit(MAX_UPLOAD_SIZE)) .app_data(web::PayloadConfig::default().limit(MAX_UPLOAD_SIZE))
.data(web::JsonConfig::default().limit(MAX_UPLOAD_SIZE)) .app_data(web::JsonConfig::default().limit(MAX_UPLOAD_SIZE))
.data(config.clone()) .app_data(web::Data::new(config.clone()))
.data(db_pool.clone()) .app_data(web::Data::new(db_pool.clone()))
.service(actix_files::Files::new( .service(actix_files::Files::new(
"/media", "/media",
config.media_dir(), config.media_dir(),

View file

@ -112,8 +112,8 @@ pub async fn create_account(
} }
// Generate RSA private key for actor // Generate RSA private key for actor
let private_key = match web::block(generate_private_key).await { let private_key = match web::block(generate_private_key).await {
Ok(private_key) => private_key, Ok(Ok(private_key)) => private_key,
Err(_) => return Err(HttpError::InternalError), _ => return Err(HttpError::InternalError),
}; };
let private_key_pem = serialize_private_key(private_key) let private_key_pem = serialize_private_key(private_key)
.map_err(|_| HttpError::InternalError)?; .map_err(|_| HttpError::InternalError)?;

View file

@ -44,10 +44,10 @@ async fn get_notifications_view(
let response = if let Some(item) = notifications.get(max_index) { let response = if let Some(item) = notifications.get(max_index) {
let pagination_header = get_pagination_header(&config.instance_url(), &item.id); let pagination_header = get_pagination_header(&config.instance_url(), &item.id);
HttpResponse::Ok() HttpResponse::Ok()
.header("Link", pagination_header) .append_header(("Link", pagination_header))
// Link header needs to be exposed // Link header needs to be exposed
// https://github.com/actix/actix-extras/issues/192 // https://github.com/actix/actix-extras/issues/192
.header("Access-Control-Expose-Headers", "Link") .append_header(("Access-Control-Expose-Headers", "Link"))
.json(notifications) .json(notifications)
} else { } else {
HttpResponse::Ok().json(notifications) HttpResponse::Ok().json(notifications)

View file

@ -1,9 +1,9 @@
use actix_web::{ use actix_web::{
body::{Body, BodySize, MessageBody, ResponseBody}, body::{BodySize, BoxBody, MessageBody},
dev::ServiceResponse,
http::StatusCode, http::StatusCode,
middleware::errhandlers::{ErrorHandlerResponse, ErrorHandlers}, middleware::{ErrorHandlerResponse, ErrorHandlers},
}; };
use actix_web::dev::ServiceResponse;
use serde_json::json; use serde_json::json;
use tokio_postgres::GenericClient; use tokio_postgres::GenericClient;
@ -27,22 +27,20 @@ pub async fn get_current_user(
} }
/// Error handler for 401 Unauthorized /// Error handler for 401 Unauthorized
pub fn create_auth_error_handler<B: MessageBody>() -> ErrorHandlers<B> { pub fn create_auth_error_handler<B: MessageBody + 'static>() -> ErrorHandlers<B> {
ErrorHandlers::new() ErrorHandlers::new()
.handler(StatusCode::UNAUTHORIZED, |mut response: ServiceResponse<B>| { .handler(StatusCode::UNAUTHORIZED, |response: ServiceResponse<B>| {
response = response.map_body(|_, body| { let response_new = response.map_body(|_, body| {
if let ResponseBody::Body(data) = &body { if let BodySize::None | BodySize::Sized(0) = body.size() {
if let BodySize::Empty = data.size() { // Insert error description if response body is empty
// Insert error description if response body is empty // https://github.com/actix/actix-extras/issues/156
// https://github.com/actix/actix-extras/issues/156 let error_data = json!({
let error_data = json!({ "message": "auth header is not present",
"message": "auth header is not present", });
}); return BoxBody::new(error_data.to_string());
return ResponseBody::Body(Body::from(error_data)).into_body(); };
} body.boxed()
}
body
}); });
Ok(ErrorHandlerResponse::Response(response)) Ok(ErrorHandlerResponse::Response(response_new.map_into_right_body()))
}) })
} }