Migrate to actix-web 4 ()

* Migrate to actix-web 4

* Change expected swagger test status code

* update tokio to 1.0

* fix clippy warnings
This commit is contained in:
Tobias Schmitz 2022-04-24 22:55:38 +02:00 committed by GitHub
parent 90fd6bbcc9
commit 374d0ca56f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 707 additions and 1001 deletions

1550
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -11,39 +11,41 @@ bundle-sqlite = ["libsqlite3-sys"]
ui = ["native-windows-gui", "native-windows-derive"]
[dependencies]
actix-files = { version = "0.5" }
actix-web = { version = "3" }
actix-web-httpauth = { version = "0.5.1" }
anyhow = "1.0.52"
actix-files = { version = "0.6" }
actix-web = { version = "4" }
actix-web-httpauth = { version = "0.6" }
actix-test = "=0.1.0-beta.13"
anyhow = "1.0.56"
ape = "0.4.0"
base64 = "0.13"
branca = "0.10.0"
cookie = { version = "0.14", features = ["signed", "key-expansion"] }
branca = "0.10.1"
cookie = { version = "0.16", features = ["signed", "key-expansion"] }
crossbeam-channel = "0.5"
diesel_migrations = { version = "1.4", features = ["sqlite"] }
futures-util = { version = "0.3" }
getopts = "0.2.21"
http = "0.2.6"
id3 = "1.0.2"
libsqlite3-sys = { version = "0.22", features = ["bundled", "bundled-windows"], optional = true }
lewton = "0.10.2"
libsqlite3-sys = { version = "0.22", features = ["bundled", "bundled-windows"], optional = true }
log = "0.4.14"
metaflac = "0.2.5"
mp3-duration = "0.1.10"
mp4ameta = "0.11.0"
num_cpus = "1.13.1"
opus_headers = "0.1.2"
pbkdf2 = "0.11"
percent-encoding = "2.1"
pbkdf2 = "0.10"
rand = "0.8"
rayon = "1.5"
regex = "1.5.4"
regex = "1.5.5"
rustfm-scrobble = "1.1.1"
serde = { version = "1.0.133", features = ["derive"] }
serde_derive = "1.0.133"
serde_json = "1.0.74"
simplelog = "0.11.1"
serde = { version = "1.0.136", features = ["derive"] }
serde_derive = "1.0.136"
serde_json = "1.0.79"
simplelog = "0.11.2"
thiserror = "1.0.30"
tokio = "1.0"
toml = "0.5"
ureq = "1.5.5"
url = "2.2"
@ -54,7 +56,7 @@ default_features = false
features = ["libsqlite3-sys", "r2d2", "sqlite", "64-column-tables"]
[dependencies.image]
version = "0.23.14"
version = "0.24.1"
default_features = false
features = ["bmp", "gif", "jpeg", "png"]
@ -64,7 +66,7 @@ native-windows-derive = {version = "1.0.4", optional = true }
[target.'cfg(unix)'.dependencies]
daemonize = "0.4.1"
sd-notify = "0.3.0"
sd-notify = "0.4.0"
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"

View file

@ -33,13 +33,13 @@ impl Manager {
if let Some(mount_dirs) = &config.mount_dirs {
self.vfs_manager
.set_mount_dirs(&mount_dirs)
.set_mount_dirs(mount_dirs)
.map_err(|_| Error::Unspecified)?;
}
if let Some(ddns_config) = &config.ydns {
self.ddns_manager
.set_config(&ddns_config)
.set_config(ddns_config)
.map_err(|_| Error::Unspecified)?;
}

View file

@ -32,7 +32,7 @@ impl Index {
settings_manager,
pending_reindex: Arc::new((
#[allow(clippy::clippy::mutex_atomic)]
#[allow(clippy::mutex_atomic)]
Mutex::new(false),
Condvar::new(),
)),

View file

@ -29,7 +29,7 @@ impl Manager {
fn get_thumbnail_path(&self, image_path: &Path, thumbnailoptions: &Options) -> PathBuf {
let hash = Manager::hash(image_path, thumbnailoptions);
let mut thumbnail_path = self.thumbnails_dir_path.clone();
thumbnail_path.push(format!("{}.jpg", hash.to_string()));
thumbnail_path.push(format!("{}.jpg", hash));
thumbnail_path
}

View file

@ -40,19 +40,19 @@ fn read_flac(path: &Path) -> Result<DynamicImage> {
fn read_mp3(path: &Path) -> Result<DynamicImage> {
let tag = id3::Tag::read_from_path(path)?;
read_id3(&path, &tag)
read_id3(path, &tag)
}
fn read_aiff(path: &Path) -> Result<DynamicImage> {
let tag = id3::Tag::read_from_aiff_path(path)?;
read_id3(&path, &tag)
read_id3(path, &tag)
}
fn read_wave(path: &Path) -> Result<DynamicImage> {
let tag = id3::Tag::read_from_wav_path(path)?;
read_id3(&path, &tag)
read_id3(path, &tag)
}
fn read_id3(path: &Path, tag: &id3::Tag) -> Result<DynamicImage> {

View file

@ -1,18 +1,19 @@
use actix_files::NamedFile;
use actix_web::body::{BoxBody, MessageBody};
use actix_web::http::header::ContentEncoding;
use actix_web::{
client::HttpError,
delete,
dev::{BodyEncoding, MessageBody, Payload, Service, ServiceRequest, ServiceResponse},
error::{BlockingError, ErrorForbidden, ErrorInternalServerError, ErrorUnauthorized},
dev::{Payload, Service, ServiceRequest, ServiceResponse},
error::{ErrorForbidden, ErrorInternalServerError, ErrorUnauthorized},
get,
http::{ContentEncoding, StatusCode},
http::StatusCode,
post, put,
web::{self, Data, Json, JsonConfig, ServiceConfig},
FromRequest, HttpMessage, HttpRequest, HttpResponse, Responder, ResponseError,
FromRequest, HttpRequest, HttpResponse, Responder, ResponseError,
};
use actix_web_httpauth::extractors::{basic::BasicAuth, bearer::BearerAuth};
use cookie::{self, *};
use futures_util::future::{err, ok, ready, Ready};
use futures_util::future::{err, ok};
use percent_encoding::percent_decode_str;
use std::future::Future;
use std::ops::Deref;
@ -113,7 +114,7 @@ impl Cookies {
}
fn add_signed(&mut self, cookie: Cookie<'static>) {
self.jar.signed(&self.key).add(cookie);
self.jar.signed_mut(&self.key).add(cookie);
}
#[allow(dead_code)]
@ -129,7 +130,6 @@ impl Cookies {
impl FromRequest for Cookies {
type Error = actix_web::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
type Config = ();
fn from_request(request: &HttpRequest, _payload: &mut Payload) -> Self::Future {
let request_cookies = match request.cookies() {
@ -168,7 +168,6 @@ struct Auth {
impl FromRequest for Auth {
type Error = actix_web::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
type Config = ();
fn from_request(request: &HttpRequest, payload: &mut Payload) -> Self::Future {
let user_manager = match request.app_data::<Data<user::Manager>>() {
@ -256,7 +255,6 @@ struct AdminRights {
impl FromRequest for AdminRights {
type Error = actix_web::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
type Config = ();
fn from_request(request: &HttpRequest, payload: &mut Payload) -> Self::Future {
let user_manager = match request.app_data::<Data<user::Manager>>() {
@ -289,11 +287,10 @@ impl FromRequest for AdminRights {
pub fn http_auth_middleware<
B: MessageBody + 'static,
S: Service<Response = ServiceResponse<B>, Request = ServiceRequest, Error = actix_web::Error>
+ 'static,
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error> + 'static,
>(
request: ServiceRequest,
service: &mut S,
service: &S,
) -> Pin<Box<dyn Future<Output = Result<ServiceResponse<B>, actix_web::Error>>>> {
let user_manager = match request.app_data::<Data<user::Manager>>() {
Some(m) => m.clone(),
@ -303,10 +300,7 @@ pub fn http_auth_middleware<
let (request, mut payload) = request.into_parts();
let auth_future = Auth::from_request(&request, &mut payload);
let cookies_future = Cookies::from_request(&request, &mut payload);
let request = match ServiceRequest::from_parts(request, payload) {
Ok(s) => s,
Err(_) => return Box::pin(err(ErrorInternalServerError(APIError::Unspecified))),
};
let request = ServiceRequest::from_parts(request, payload);
let response_future = service.call(request);
Box::pin(async move {
@ -339,7 +333,7 @@ fn add_auth_cookies<T>(
cookies: &Cookies,
username: &str,
is_admin: bool,
) -> Result<(), HttpError> {
) -> Result<(), http::Error> {
let mut cookies = cookies.clone();
cookies.add_signed(
@ -384,25 +378,20 @@ struct MediaFile {
impl MediaFile {
fn new(named_file: NamedFile) -> Self {
Self {
named_file: named_file,
}
Self { named_file }
}
}
impl Responder for MediaFile {
type Error = actix_web::Error;
type Future = Ready<Result<HttpResponse, actix_web::Error>>;
type Body = BoxBody;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
let mut response = self.named_file.into_response(req);
if let Ok(r) = response.as_mut() {
// Intentionally turn off content encoding for media files because:
// 1. There is little value in compressing files that are already compressed (mp3, jpg, etc.)
// 2. The Content-Length header is incompatible with content encoding (other than identity), and can be valuable for clients
r.encoding(ContentEncoding::Identity);
}
return ready(response);
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
// Intentionally turn off content encoding for media files because:
// 1. There is little value in compressing files that are already compressed (mp3, jpg, etc.)
// 2. The Content-Length header is incompatible with content encoding (other than identity), and can be valuable for clients
self.named_file
.set_content_encoding(ContentEncoding::Identity)
.into_response(req)
}
}
@ -412,10 +401,10 @@ where
I: Send + 'static,
E: Send + std::fmt::Debug + 'static + Into<APIError>,
{
actix_web::web::block(f).await.map_err(|e| match e {
BlockingError::Error(e) => e.into(),
BlockingError::Canceled => APIError::Unspecified,
})
actix_web::web::block(f)
.await
.map_err(|_| APIError::Unspecified)
.and_then(|r| r.map_err(|e| e.into()))
}
#[get("/version")]
@ -488,8 +477,8 @@ async fn put_mount_dirs(
new_mount_dirs: Json<Vec<dto::MountDir>>,
) -> Result<HttpResponse, APIError> {
let new_mount_dirs: Vec<MountDir> = new_mount_dirs
.to_owned()
.into_iter()
.iter()
.cloned()
.map(|m| m.into())
.collect();
block(move || vfs_manager.set_mount_dirs(&new_mount_dirs)).await?;
@ -646,7 +635,7 @@ async fn browse(
path: web::Path<String>,
) -> Result<Json<Vec<index::CollectionFile>>, APIError> {
let result = block(move || {
let path = percent_decode_str(&(path.0)).decode_utf8_lossy();
let path = percent_decode_str(&path).decode_utf8_lossy();
index.browse(Path::new(path.as_ref()))
})
.await?;
@ -666,7 +655,7 @@ async fn flatten(
path: web::Path<String>,
) -> Result<Json<Vec<index::Song>>, APIError> {
let songs = block(move || {
let path = percent_decode_str(&(path.0)).decode_utf8_lossy();
let path = percent_decode_str(&path).decode_utf8_lossy();
index.flatten(Path::new(path.as_ref()))
})
.await?;
@ -712,7 +701,7 @@ async fn get_audio(
) -> Result<MediaFile, APIError> {
let audio_path = block(move || {
let vfs = vfs_manager.get_vfs()?;
let path = percent_decode_str(&(path.0)).decode_utf8_lossy();
let path = percent_decode_str(&path).decode_utf8_lossy();
vfs.virtual_to_real(Path::new(path.as_ref()))
.map_err(|_| APIError::VFSPathNotFound)
})
@ -734,7 +723,7 @@ async fn get_thumbnail(
let thumbnail_path = block(move || {
let vfs = vfs_manager.get_vfs()?;
let path = percent_decode_str(&(path.0)).decode_utf8_lossy();
let path = percent_decode_str(&path).decode_utf8_lossy();
let image_path = vfs
.virtual_to_real(Path::new(path.as_ref()))
.map_err(|_| APIError::VFSPathNotFound)?;
@ -806,7 +795,7 @@ async fn lastfm_now_playing(
if !user_manager.is_lastfm_linked(&auth.username) {
return Err(APIError::LastFMAccountNotLinked);
}
let path = percent_decode_str(&(path.0)).decode_utf8_lossy();
let path = percent_decode_str(&path).decode_utf8_lossy();
lastfm_manager.now_playing(&auth.username, Path::new(path.as_ref()))?;
Ok(())
})
@ -825,7 +814,7 @@ async fn lastfm_scrobble(
if !user_manager.is_lastfm_linked(&auth.username) {
return Err(APIError::LastFMAccountNotLinked);
}
let path = percent_decode_str(&(path.0)).decode_utf8_lossy();
let path = percent_decode_str(&path).decode_utf8_lossy();
lastfm_manager.scrobble(&auth.username, Path::new(path.as_ref()))?;
Ok(())
})

View file

@ -1,10 +1,9 @@
use actix_web::{
middleware::{normalize::TrailingSlash, Compress, Logger, NormalizePath},
middleware::{Compress, Logger, NormalizePath},
rt::System,
web::{self, ServiceConfig},
App as ActixApp, HttpServer,
};
use anyhow::*;
use log::error;
use crate::app::App;
@ -31,7 +30,7 @@ pub fn make_config(app: App) -> impl FnOnce(&mut ServiceConfig) + Clone {
web::scope("/api")
.configure(api::make_config())
.wrap_fn(api::http_auth_middleware)
.wrap(NormalizePath::new(TrailingSlash::Trim)),
.wrap(NormalizePath::trim()),
)
.service(
actix_files::Files::new("/swagger", app.swagger_dir_path)
@ -46,9 +45,9 @@ pub fn make_config(app: App) -> impl FnOnce(&mut ServiceConfig) + Clone {
}
}
pub fn run(app: App) -> Result<()> {
System::run(move || {
let address = format!("0.0.0.0:{}", app.port);
pub fn run(app: App) -> anyhow::Result<()> {
let address = ("0.0.0.0", app.port);
System::new().block_on(
HttpServer::new(move || {
ActixApp::new()
.wrap(Logger::default())
@ -57,9 +56,11 @@ pub fn run(app: App) -> Result<()> {
})
.disable_signals()
.bind(address)
.map(|server| server.run())
.map_err(|e| error!("Error starting HTTP server: {:?}", e))
.ok();
})?;
.map_err(|e| {
error!("Error starting HTTP server: {:?}", e);
e
})?
.run()
)?;
Ok(())
}

View file

@ -1,8 +1,7 @@
use actix_test::TestServer;
use actix_web::{
middleware::{Compress, Logger},
rt::{System, SystemRunner},
test,
test::*,
web::Bytes,
App as ActixApp,
};
@ -44,7 +43,7 @@ impl ActixTestService {
.timeout(std::time::Duration::from_secs(30));
for (name, value) in request.headers() {
actix_request = actix_request.set_header(name, value.clone());
actix_request = actix_request.insert_header((name, value.clone()));
}
if let Some(ref authorization) = self.authorization {
@ -92,8 +91,8 @@ impl TestService for ActixTestService {
let app = App::new(5050, paths).unwrap();
let system_runner = System::new("test");
let server = test::start(move || {
let system_runner = System::new();
let server = actix_test::start(move || {
let config = make_config(app.clone());
ActixApp::new()
.wrap(Logger::default())

View file

@ -61,7 +61,7 @@ pub enum ThumbnailSize {
Native,
}
#[allow(clippy::clippy::clippy::from_over_into)]
#[allow(clippy::from_over_into)]
impl Into<Option<u32>> for ThumbnailSize {
fn into(self) -> Option<u32> {
match self {

View file

@ -8,8 +8,7 @@ fn can_get_swagger_index() {
let mut service = ServiceType::new(&test_name!());
let request = protocol::swagger_index();
let response = service.fetch(&request);
let status = response.status();
assert_eq!(status, StatusCode::FOUND);
assert_eq!(response.status(), StatusCode::OK);
}
#[test]