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"
edition = "2018"
rust-version = "1.53"
rust-version = "1.54"
publish = false
default-run = "mitra"
[dependencies]
# Used to handle incoming HTTP requests
actix-cors = "0.5.4"
actix-files = "0.5.0"
actix-web = "3.3.3"
actix-web-httpauth = "0.5.1"
actix-cors = "0.6.1"
actix-files = "0.6.0"
actix-web = "4.0.1"
actix-web-httpauth = "0.6.0"
# Used for managing async tasks
actix-rt = "1.1.1"
actix-rt = "2.7.0"
# Used for HTML sanitization
ammonia = "3.1.2"
# 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
# Versions greater than beta.2 require Rust 1.54
clap = { version = "3.0.0-beta.2", default-features = false, features = ["std", "derive"] }
# Used for pooling database connections (compatible with tokio 0.2)
deadpool = "0.5.2"
deadpool-postgres = { version = "0.5.6", default-features = false }
# Used for pooling database connections
deadpool = "0.9.2"
deadpool-postgres = { version = "0.10.2", default-features = false }
# Used to read .env files
dotenv = "0.15.0"
# Used to work with hexadecimal strings
@ -46,9 +46,9 @@ regex = "1.5.4"
# Used to generate random numbers
rand = "0.8.4"
# 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
reqwest = { version = "0.10.10", features = ["json"] }
reqwest = { version = "0.11.10", features = ["json", "multipart"] }
# Used for working with RSA keys
rsa = "0.5.0"
pem = "1.0.2"
@ -57,8 +57,7 @@ rust-argon2 = "0.8.3"
# Used for working with ethereum keys
secp256k1 = { version = "0.20.3", features = ["rand", "rand-std"] }
# Used for serialization/deserialization
# https://github.com/rust-db/refinery/issues/160
serde = { version = "=1.0.117", features = ["derive"] }
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0"
# Used to parse config file
serde_yaml = "0.8.17"
@ -68,15 +67,15 @@ sha2 = "0.9.5"
siwe = { git = "https://github.com/silverpill/siwe-rs", rev = "6589eb6fdcffbfb9ee2880906014f5cf71f0f441" }
# Used for creating error types
thiserror = "1.0.24"
# Async runtime ( required for #[tokio::main] )
tokio = { version = "0.2.25", features = ["macros"] }
# Used for working with Postgresql database (compatible with tokio 0.2)
tokio-postgres = { version = "0.5.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-protocol = "0.5.3"
# Async runtime
tokio = { version = "1.17.0", features = ["macros"] }
# Used for working with Postgresql database
tokio-postgres = { version = "0.7.5", features = ["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.6.1"
# Used to construct PostgreSQL queries
postgres_query = "0.3.3"
postgres_query_macro = "0.3.1"
postgres_query = { git = "https://github.com/nolanderc/rust-postgres-query", rev = "b4422051c8a31fbba4a35f88004c1cefb1878dd5" }
postgres_query_macro = { git = "https://github.com/nolanderc/rust-postgres-query", rev = "b4422051c8a31fbba4a35f88004c1cefb1878dd5" }
# Used to work with URLs
url = "2.2.2"
# Used to generate lexicographically sortable IDs
@ -84,7 +83,9 @@ ulid = { version = "0.4.1", features = ["uuid"] }
# Used to work with UUIDs
uuid = { version = "0.8.2", features = ["serde", "v4"] }
# 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]
serial_test = "0.5.1"

View file

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

View file

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

View file

@ -1,4 +1,6 @@
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 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 {
deadpool_postgres::Pool::new(
deadpool_postgres::Manager::new(
database_url.parse().expect("invalid database URL"),
tokio_postgres::NoTls,
),
// https://wiki.postgresql.org/wiki/Number_Of_Database_Connections
num_cpus::get() * 2,
)
let manager = deadpool_postgres::Manager::new(
database_url.parse().expect("invalid database URL"),
tokio_postgres::NoTls,
);
// https://wiki.postgresql.org/wiki/Number_Of_Database_Connections
let pool_size = 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)
-> Result<deadpool_postgres::Client, DatabaseError>
{

View file

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

View file

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

View file

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

View file

@ -112,8 +112,8 @@ pub async fn create_account(
}
// Generate RSA private key for actor
let private_key = match web::block(generate_private_key).await {
Ok(private_key) => private_key,
Err(_) => return Err(HttpError::InternalError),
Ok(Ok(private_key)) => private_key,
_ => return Err(HttpError::InternalError),
};
let private_key_pem = serialize_private_key(private_key)
.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 pagination_header = get_pagination_header(&config.instance_url(), &item.id);
HttpResponse::Ok()
.header("Link", pagination_header)
.append_header(("Link", pagination_header))
// Link header needs to be exposed
// https://github.com/actix/actix-extras/issues/192
.header("Access-Control-Expose-Headers", "Link")
.append_header(("Access-Control-Expose-Headers", "Link"))
.json(notifications)
} else {
HttpResponse::Ok().json(notifications)

View file

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