From 58b4eb368162feb4a85e259f7cf5fcdc3624742d Mon Sep 17 00:00:00 2001 From: asonix Date: Sun, 12 May 2019 12:19:49 -0500 Subject: [PATCH] Update to latest actix-web --- Cargo.toml | 10 +- examples/fetch.rs | 30 +++--- examples/resolver.rs | 26 +++-- src/lib.rs | 238 +++++++++++++++++++++++++++---------------- 4 files changed, 187 insertions(+), 117 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9327dd0..76465f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "actix-webfinger" description = "Types and helpers to create and fetch Webfinger resources" -version = "0.1.0" +version = "0.2.0-beta.1" license = "GPL-3.0" authors = ["asonix "] repository = "https://git.asonix.dog/asonix/actix-webfinger" @@ -9,14 +9,16 @@ readme = "README.md" edition = "2018" [dependencies] -actix = "0.7" -actix-web = "0.7" +actix-http = "0.1.5" +actix-service = "0.3.0" +actix-web = "1.0.0-beta.3" failure = "0.1" futures = "0.1" serde = "1.0" serde_derive = "1.0" [dev-dependencies] -actix-web = { version = "0.7", features = ["ssl"] } +actix = "0.8" +actix-web = { version = "1.0.0-beta.3", features = ["ssl"] } openssl = "0.10" serde_json = "1.0" diff --git a/examples/fetch.rs b/examples/fetch.rs index 4162000..da3f87e 100644 --- a/examples/fetch.rs +++ b/examples/fetch.rs @@ -1,24 +1,28 @@ -use actix::{Actor, System}; -use actix_web::client::ClientConnector; +use actix::System; +use actix_web::client::Connector; use actix_webfinger::Webfinger; -use futures::Future; +use futures::{future::lazy, Future}; use openssl::ssl::{SslConnector, SslMethod}; +use std::error::Error; -fn main() { +fn main() -> Result<(), Box> { let sys = System::new("sir-boops"); - let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); - let conn = ClientConnector::with_connector(ssl_conn).start(); + let ssl_conn = SslConnector::builder(SslMethod::tls())?.build(); + let conn = Connector::new().ssl(ssl_conn).finish(); - let fut = Webfinger::fetch(conn, "asonix@asonix.dog", "localhost:8000", false) - .map(move |w: Webfinger| { - println!("asonix's webfinger:\n{:#?}", w); + let fut = lazy(move || { + Webfinger::fetch(conn, "asonix@asonix.dog", "localhost:8000", false) + .map(move |w: Webfinger| { + println!("asonix's webfinger:\n{:#?}", w); - System::current().stop(); - }) - .map_err(|e| eprintln!("Error: {}", e)); + System::current().stop(); + }) + .map_err(|e| eprintln!("Error: {}", e)) + }); actix::spawn(fut); - let _ = sys.run(); + sys.run()?; + Ok(()) } diff --git a/examples/resolver.rs b/examples/resolver.rs index 242c373..a51c17d 100644 --- a/examples/resolver.rs +++ b/examples/resolver.rs @@ -1,6 +1,7 @@ -use actix_web::server; +use actix_web::{web::Data, App, HttpServer}; use actix_webfinger::{Resolver, Webfinger}; use futures::{future::IntoFuture, Future}; +use std::error::Error; #[derive(Clone, Debug)] pub struct MyState { @@ -9,13 +10,13 @@ pub struct MyState { pub struct MyResolver; -impl Resolver for MyResolver { +impl Resolver> for MyResolver { type Error = actix_web::error::JsonPayloadError; fn find( account: &str, domain: &str, - state: &MyState, + state: &Data, ) -> Box, Error = Self::Error>> { let w = if domain == state.domain { Some(Webfinger::new(&format!("{}@{}", account, domain))) @@ -27,13 +28,16 @@ impl Resolver for MyResolver { } } -fn main() { - server::new(|| { - actix_webfinger::app::(MyState { - domain: "asonix.dog".to_owned(), - }) +fn main() -> Result<(), Box> { + HttpServer::new(|| { + App::new() + .data(MyState { + domain: "asonix.dog".to_owned(), + }) + .service(actix_webfinger::resource::<_, MyResolver>()) }) - .bind("127.0.0.1:8000") - .unwrap() - .run(); + .bind("127.0.0.1:8000")? + .run()?; + + Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 6ca8def..1933c8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,106 +9,117 @@ //! //! ```toml //! [dependencies] -//! actix = "0.7" -//! actix-web = "0.7" -//! actix-webfinger = "0.1" +//! actix = "0.8" +//! actix-web = "1.0.0-beta.3" +//! actix-webfinger = "0.2.0-beta.1" //! ``` //! //! Then use it in your application //! //! #### Client Example //! ```rust,ignore -//! use actix::{Actor, System}; -//! use actix_web::client::ClientConnector; +//! use actix::System; +//! use actix_web::client::Connector; //! use actix_webfinger::Webfinger; -//! use futures::Future; +//! use futures::{future::lazy, Future}; //! use openssl::ssl::{SslConnector, SslMethod}; -//! -//! fn main() { +//! use std::error::Error; +//! +//! fn main() -> Result<(), Box> { //! let sys = System::new("asonix"); -//! -//! let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); -//! let conn = ClientConnector::with_connector(ssl_conn).start(); -//! -//! let fut = Webfinger::fetch(conn, "asonix@asonix.dog", "localhost:8000", false) -//! .map(move |w: Webfinger| { -//! println!("asonix's webfinger:\n{:#?}", w); -//! -//! System::current().stop(); -//! }) -//! .map_err(|e| eprintln!("Error: {}", e)); -//! +//! +//! let ssl_conn = SslConnector::builder(SslMethod::tls())?.build(); +//! let conn = Connector::new().ssl(ssl_conn).finish(); +//! +//! let fut = lazy(move || { +//! Webfinger::fetch(conn, "asonix@asonix.dog", "localhost:8000", false) +//! .map(move |w: Webfinger| { +//! println!("asonix's webfinger:\n{:#?}", w); +//! +//! System::current().stop(); +//! }) +//! .map_err(|e| eprintln!("Error: {}", e)) +//! }); +//! //! actix::spawn(fut); -//! -//! let _ = sys.run(); +//! +//! sys.run()?; +//! Ok(()) //! } //! ``` //! //! #### Server Example //! ```rust,ignore -//! use actix_web::server; +//! use actix_web::{web::Data, App, HttpServer}; //! use actix_webfinger::{Resolver, Webfinger}; //! use futures::{future::IntoFuture, Future}; -//! +//! use std::error::Error; +//! //! #[derive(Clone, Debug)] //! pub struct MyState { //! domain: String, //! } -//! +//! //! pub struct MyResolver; -//! -//! impl Resolver for MyResolver { +//! +//! impl Resolver> for MyResolver { //! type Error = actix_web::error::JsonPayloadError; -//! +//! //! fn find( //! account: &str, //! domain: &str, -//! state: &MyState, +//! state: &Data, //! ) -> Box, Error = Self::Error>> { //! let w = if domain == state.domain { //! Some(Webfinger::new(&format!("{}@{}", account, domain))) //! } else { //! None //! }; -//! +//! //! Box::new(Ok(w).into_future()) //! } //! } -//! -//! fn main() { -//! server::new(|| { -//! actix_webfinger::app::(MyState { -//! domain: "asonix.dog".to_owned(), -//! }) +//! +//! fn main() -> Result<(), Box> { +//! HttpServer::new(|| { +//! App::new() +//! .data(MyState { +//! domain: "asonix.dog".to_owned(), +//! }) +//! .service(actix_webfinger::resource::<_, MyResolver>()) //! }) -//! .bind("127.0.0.1:8000") -//! .unwrap() -//! .run(); +//! .bind("127.0.0.1:8000")? +//! .run()?; +//! +//! Ok(()) //! } //! ``` //! //! ### Contributing //! Feel free to open issues for anything you find an issue with. Please note that any contributed code will be licensed under the GPLv3. -//! +//! //! ### License -//! +//! //! Copyright © 2019 Riley Trautman -//! +//! //! Actix Webfinger is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. -//! +//! //! Actix Webfinger is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. This file is part of Tokio ZMQ. -//! +//! //! You should have received a copy of the GNU General Public License along with Actix Webfinger. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). -use actix::Addr; +use actix_http::client::{Connect, ConnectError, Connection}; +use actix_service::Service; use actix_web::{ - client::{self, ClientConnector}, + client::Client, + dev::RequestHead, error::ResponseError, + guard::Guard, http::Method, - pred::Predicate, - App, HttpMessage, HttpResponse, Query, Request, State, + web::{get, Query}, + FromRequest, HttpResponse, Resource, }; use failure::Fail; -use futures::{future::IntoFuture, Future}; +use futures::Future; use serde_derive::{Deserialize, Serialize}; /// A predicate for Actix Web route filters @@ -124,21 +135,21 @@ use serde_derive::{Deserialize, Serialize}; /// /// ```rust,ignore /// use actix_web::App; -/// use actix_webfinger::WebfingerPredicate; +/// use actix_webfinger::WebfingerGuard; /// /// let app = App::new() /// .resource("/.well-known/webfinger", |r| { /// r.route() -/// .filter(WebfingerPredicate) +/// .filter(WebfingerGuard) /// .with(your_route_handler) /// }) /// .finish(); /// ``` -pub struct WebfingerPredicate; +pub struct WebfingerGuard; -impl Predicate for WebfingerPredicate { - fn check(&self, req: &Request, _: &S) -> bool { - if let Some(val) = req.headers().get("Accept") { +impl Guard for WebfingerGuard { + fn check(&self, request: &RequestHead) -> bool { + if let Some(val) = request.headers().get("Accept") { if let Ok(s) = val.to_str() { return s.split(",").any(|v| { let v = if let Some(index) = v.find(';') { @@ -152,7 +163,7 @@ impl Predicate for WebfingerPredicate { || trimmed == "application/json" || trimmed == "application/*" || trimmed == "*/*" - }) && req.method() == Method::GET; + }) && request.method == Method::GET; } } @@ -160,26 +171,53 @@ impl Predicate for WebfingerPredicate { } } -/// A simple way to mount the webfinger app to your Actix Web application +/// A simple way to mount the webfinger service to your Actix Web application /// /// ```rust,ignore -/// use actix_web::server; -/// use actix_webfinger::app; +/// use actix_web::HttpServer; /// -/// server::new(|| { -/// app::<_, MyResolver>(my_state) +/// HttpServer::new(|| { +/// App::new() +/// .service(actix_webfinger::resource::<_, MyResolver>()) /// }) /// .bind("127.0.0.1:8000")? -/// .run(); +/// .start(); /// ``` -pub fn app(state: S) -> App +pub fn resource() -> Resource where R: Resolver + 'static, - S: 'static, + S: FromRequest + 'static, { - App::with_state(state).resource("/.well-known/webfinger", |r| { - r.route().filter(WebfingerPredicate).with(R::endpoint) - }) + actix_web::web::resource("/.well-known/webfinger") + .guard(WebfingerGuard) + .route(get().to(R::endpoint)) +} + +/// A simple way to mount the webfinger service inside the /.well-known scope +/// +/// ```rust,ignore +/// use actix_web::{App, HttpServer, web::scope}; +/// use actix_webfinger::resource; +/// +/// HttpServer::new(|| { +/// App::new() +/// .data(()) +/// .service( +/// scope("/") +/// .service(resource::<(), MyResolver>()) +/// ) +/// }) +/// .bind("127.0.0.1:8000") +/// .start(); +/// ``` +pub fn scoped() -> Resource +where + R: Resolver + 'static, + S: FromRequest + 'static, +{ + actix_web::web::resource("/webfinger") + .guard(WebfingerGuard) + .route(get().to(R::endpoint)) } /// The error created if the webfinger resource query is malformed @@ -290,7 +328,10 @@ pub struct WebfingerQuery { /// /// struct MyResolver; /// -/// impl Resolver for MyResolver { +/// impl Resolver for MyResolver +/// where +/// S: FromRequest, +/// { /// type Error = CustomError; /// /// fn find( @@ -308,19 +349,27 @@ pub struct WebfingerQuery { /// } /// } /// -/// fn main() { -/// server::new(|| { +/// fn main() -> Result<(), Box> { +/// let sys = System::new("asonix"); +/// +/// HttpServer::new(|| { /// App::new() -/// .resource("/.well-known/webfinger", |r| { -/// r.with(>::endpoint) -/// }) +/// .data(()) +/// .service(resource::, MyResolver>()) /// }) /// .bind("127.0.0.1:8000")? -/// .run(); +/// .start(); +/// +/// sys.run()?; +/// +/// Ok(()) /// } /// ``` -pub trait Resolver { - type Error: ResponseError; +pub trait Resolver +where + S: FromRequest, +{ + type Error: ResponseError + 'static; fn find( account: &str, @@ -329,7 +378,7 @@ pub trait Resolver { ) -> Box, Error = Self::Error>>; fn endpoint( - (query, state): (Query, State), + (query, state): (Query, S), ) -> Box> { let WebfingerResource { account, domain } = query.into_inner().resource; @@ -547,14 +596,19 @@ impl Webfinger { /// Fetch a webfinger with subject `user` from a given `domain` /// - /// This method takes an `Addr` so derivative works can provide their own SSL + /// This method takes an `Service` so derivative works can provide their own SSL /// connector implemenation (currently with OpenSSL or Rustls) - pub fn fetch( - conn: Addr, + pub fn fetch( + conn: T, user: &str, domain: &str, https: bool, - ) -> Box> { + ) -> Box> + where + T: Service + Clone + 'static, + ::Response: Connection, + ::Future: 'static, + { let url = format!( "{}://{}/.well-known/webfinger?resource=acct:{}", if https { "https" } else { "http" }, @@ -562,21 +616,27 @@ impl Webfinger { user ); - let fut = client::get(url) - .with_connector(conn) + let fut = Client::build() + .connector(conn) .header("Accept", "application/jrd+json") .finish() - .into_future() - .and_then(|r| { - r.send() - .from_err() - .and_then(|res| res.json::().from_err()) - }); + .get(url) + .send() + .map_err(|_| FetchError::Send) + .and_then(|mut res| res.json::().map_err(|_| FetchError::Parse)); Box::new(fut) } } +#[derive(Clone, Debug, Fail)] +pub enum FetchError { + #[fail(display = "Failed to send request")] + Send, + #[fail(display = "Failed to parse response JSON")] + Parse, +} + #[cfg(test)] mod tests { use crate::{Webfinger, WebfingerQuery};