Update to latest actix-web

This commit is contained in:
asonix 2019-05-12 12:19:49 -05:00
parent f739f6729b
commit 58b4eb3681
4 changed files with 187 additions and 117 deletions

View file

@ -1,7 +1,7 @@
[package] [package]
name = "actix-webfinger" name = "actix-webfinger"
description = "Types and helpers to create and fetch Webfinger resources" description = "Types and helpers to create and fetch Webfinger resources"
version = "0.1.0" version = "0.2.0-beta.1"
license = "GPL-3.0" license = "GPL-3.0"
authors = ["asonix <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
repository = "https://git.asonix.dog/asonix/actix-webfinger" repository = "https://git.asonix.dog/asonix/actix-webfinger"
@ -9,14 +9,16 @@ readme = "README.md"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
actix = "0.7" actix-http = "0.1.5"
actix-web = "0.7" actix-service = "0.3.0"
actix-web = "1.0.0-beta.3"
failure = "0.1" failure = "0.1"
futures = "0.1" futures = "0.1"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
[dev-dependencies] [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" openssl = "0.10"
serde_json = "1.0" serde_json = "1.0"

View file

@ -1,24 +1,28 @@
use actix::{Actor, System}; use actix::System;
use actix_web::client::ClientConnector; use actix_web::client::Connector;
use actix_webfinger::Webfinger; use actix_webfinger::Webfinger;
use futures::Future; use futures::{future::lazy, Future};
use openssl::ssl::{SslConnector, SslMethod}; use openssl::ssl::{SslConnector, SslMethod};
use std::error::Error;
fn main() { fn main() -> Result<(), Box<dyn Error>> {
let sys = System::new("sir-boops"); let sys = System::new("sir-boops");
let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); let ssl_conn = SslConnector::builder(SslMethod::tls())?.build();
let conn = ClientConnector::with_connector(ssl_conn).start(); let conn = Connector::new().ssl(ssl_conn).finish();
let fut = Webfinger::fetch(conn, "asonix@asonix.dog", "localhost:8000", false) let fut = lazy(move || {
.map(move |w: Webfinger| { Webfinger::fetch(conn, "asonix@asonix.dog", "localhost:8000", false)
println!("asonix's webfinger:\n{:#?}", w); .map(move |w: Webfinger| {
println!("asonix's webfinger:\n{:#?}", w);
System::current().stop(); System::current().stop();
}) })
.map_err(|e| eprintln!("Error: {}", e)); .map_err(|e| eprintln!("Error: {}", e))
});
actix::spawn(fut); actix::spawn(fut);
let _ = sys.run(); sys.run()?;
Ok(())
} }

View file

@ -1,6 +1,7 @@
use actix_web::server; use actix_web::{web::Data, App, HttpServer};
use actix_webfinger::{Resolver, Webfinger}; use actix_webfinger::{Resolver, Webfinger};
use futures::{future::IntoFuture, Future}; use futures::{future::IntoFuture, Future};
use std::error::Error;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MyState { pub struct MyState {
@ -9,13 +10,13 @@ pub struct MyState {
pub struct MyResolver; pub struct MyResolver;
impl Resolver<MyState> for MyResolver { impl Resolver<Data<MyState>> for MyResolver {
type Error = actix_web::error::JsonPayloadError; type Error = actix_web::error::JsonPayloadError;
fn find( fn find(
account: &str, account: &str,
domain: &str, domain: &str,
state: &MyState, state: &Data<MyState>,
) -> Box<dyn Future<Item = Option<Webfinger>, Error = Self::Error>> { ) -> Box<dyn Future<Item = Option<Webfinger>, Error = Self::Error>> {
let w = if domain == state.domain { let w = if domain == state.domain {
Some(Webfinger::new(&format!("{}@{}", account, domain))) Some(Webfinger::new(&format!("{}@{}", account, domain)))
@ -27,13 +28,16 @@ impl Resolver<MyState> for MyResolver {
} }
} }
fn main() { fn main() -> Result<(), Box<dyn Error>> {
server::new(|| { HttpServer::new(|| {
actix_webfinger::app::<MyState, MyResolver>(MyState { App::new()
domain: "asonix.dog".to_owned(), .data(MyState {
}) domain: "asonix.dog".to_owned(),
})
.service(actix_webfinger::resource::<_, MyResolver>())
}) })
.bind("127.0.0.1:8000") .bind("127.0.0.1:8000")?
.unwrap() .run()?;
.run();
Ok(())
} }

View file

@ -9,106 +9,117 @@
//! //!
//! ```toml //! ```toml
//! [dependencies] //! [dependencies]
//! actix = "0.7" //! actix = "0.8"
//! actix-web = "0.7" //! actix-web = "1.0.0-beta.3"
//! actix-webfinger = "0.1" //! actix-webfinger = "0.2.0-beta.1"
//! ``` //! ```
//! //!
//! Then use it in your application //! Then use it in your application
//! //!
//! #### Client Example //! #### Client Example
//! ```rust,ignore //! ```rust,ignore
//! use actix::{Actor, System}; //! use actix::System;
//! use actix_web::client::ClientConnector; //! use actix_web::client::Connector;
//! use actix_webfinger::Webfinger; //! use actix_webfinger::Webfinger;
//! use futures::Future; //! use futures::{future::lazy, Future};
//! use openssl::ssl::{SslConnector, SslMethod}; //! use openssl::ssl::{SslConnector, SslMethod};
//! //! use std::error::Error;
//! fn main() { //!
//! fn main() -> Result<(), Box<dyn Error>> {
//! let sys = System::new("asonix"); //! let sys = System::new("asonix");
//! //!
//! let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); //! let ssl_conn = SslConnector::builder(SslMethod::tls())?.build();
//! let conn = ClientConnector::with_connector(ssl_conn).start(); //! let conn = Connector::new().ssl(ssl_conn).finish();
//! //!
//! let fut = Webfinger::fetch(conn, "asonix@asonix.dog", "localhost:8000", false) //! let fut = lazy(move || {
//! .map(move |w: Webfinger| { //! Webfinger::fetch(conn, "asonix@asonix.dog", "localhost:8000", false)
//! println!("asonix's webfinger:\n{:#?}", w); //! .map(move |w: Webfinger| {
//! //! println!("asonix's webfinger:\n{:#?}", w);
//! System::current().stop(); //!
//! }) //! System::current().stop();
//! .map_err(|e| eprintln!("Error: {}", e)); //! })
//! //! .map_err(|e| eprintln!("Error: {}", e))
//! });
//!
//! actix::spawn(fut); //! actix::spawn(fut);
//! //!
//! let _ = sys.run(); //! sys.run()?;
//! Ok(())
//! } //! }
//! ``` //! ```
//! //!
//! #### Server Example //! #### Server Example
//! ```rust,ignore //! ```rust,ignore
//! use actix_web::server; //! use actix_web::{web::Data, App, HttpServer};
//! use actix_webfinger::{Resolver, Webfinger}; //! use actix_webfinger::{Resolver, Webfinger};
//! use futures::{future::IntoFuture, Future}; //! use futures::{future::IntoFuture, Future};
//! //! use std::error::Error;
//!
//! #[derive(Clone, Debug)] //! #[derive(Clone, Debug)]
//! pub struct MyState { //! pub struct MyState {
//! domain: String, //! domain: String,
//! } //! }
//! //!
//! pub struct MyResolver; //! pub struct MyResolver;
//! //!
//! impl Resolver<MyState> for MyResolver { //! impl Resolver<Data<MyState>> for MyResolver {
//! type Error = actix_web::error::JsonPayloadError; //! type Error = actix_web::error::JsonPayloadError;
//! //!
//! fn find( //! fn find(
//! account: &str, //! account: &str,
//! domain: &str, //! domain: &str,
//! state: &MyState, //! state: &Data<MyState>,
//! ) -> Box<dyn Future<Item = Option<Webfinger>, Error = Self::Error>> { //! ) -> Box<dyn Future<Item = Option<Webfinger>, Error = Self::Error>> {
//! let w = if domain == state.domain { //! let w = if domain == state.domain {
//! Some(Webfinger::new(&format!("{}@{}", account, domain))) //! Some(Webfinger::new(&format!("{}@{}", account, domain)))
//! } else { //! } else {
//! None //! None
//! }; //! };
//! //!
//! Box::new(Ok(w).into_future()) //! Box::new(Ok(w).into_future())
//! } //! }
//! } //! }
//! //!
//! fn main() { //! fn main() -> Result<(), Box<dyn Error>> {
//! server::new(|| { //! HttpServer::new(|| {
//! actix_webfinger::app::<MyState, MyResolver>(MyState { //! App::new()
//! domain: "asonix.dog".to_owned(), //! .data(MyState {
//! }) //! domain: "asonix.dog".to_owned(),
//! })
//! .service(actix_webfinger::resource::<_, MyResolver>())
//! }) //! })
//! .bind("127.0.0.1:8000") //! .bind("127.0.0.1:8000")?
//! .unwrap() //! .run()?;
//! .run(); //!
//! Ok(())
//! } //! }
//! ``` //! ```
//! //!
//! ### Contributing //! ### 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. //! 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 //! ### License
//! //!
//! Copyright © 2019 Riley Trautman //! 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 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. //! 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/). //! 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::{ use actix_web::{
client::{self, ClientConnector}, client::Client,
dev::RequestHead,
error::ResponseError, error::ResponseError,
guard::Guard,
http::Method, http::Method,
pred::Predicate, web::{get, Query},
App, HttpMessage, HttpResponse, Query, Request, State, FromRequest, HttpResponse, Resource,
}; };
use failure::Fail; use failure::Fail;
use futures::{future::IntoFuture, Future}; use futures::Future;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
/// A predicate for Actix Web route filters /// A predicate for Actix Web route filters
@ -124,21 +135,21 @@ use serde_derive::{Deserialize, Serialize};
/// ///
/// ```rust,ignore /// ```rust,ignore
/// use actix_web::App; /// use actix_web::App;
/// use actix_webfinger::WebfingerPredicate; /// use actix_webfinger::WebfingerGuard;
/// ///
/// let app = App::new() /// let app = App::new()
/// .resource("/.well-known/webfinger", |r| { /// .resource("/.well-known/webfinger", |r| {
/// r.route() /// r.route()
/// .filter(WebfingerPredicate) /// .filter(WebfingerGuard)
/// .with(your_route_handler) /// .with(your_route_handler)
/// }) /// })
/// .finish(); /// .finish();
/// ``` /// ```
pub struct WebfingerPredicate; pub struct WebfingerGuard;
impl<S> Predicate<S> for WebfingerPredicate { impl Guard for WebfingerGuard {
fn check(&self, req: &Request, _: &S) -> bool { fn check(&self, request: &RequestHead) -> bool {
if let Some(val) = req.headers().get("Accept") { if let Some(val) = request.headers().get("Accept") {
if let Ok(s) = val.to_str() { if let Ok(s) = val.to_str() {
return s.split(",").any(|v| { return s.split(",").any(|v| {
let v = if let Some(index) = v.find(';') { let v = if let Some(index) = v.find(';') {
@ -152,7 +163,7 @@ impl<S> Predicate<S> for WebfingerPredicate {
|| trimmed == "application/json" || trimmed == "application/json"
|| trimmed == "application/*" || trimmed == "application/*"
|| trimmed == "*/*" || trimmed == "*/*"
}) && req.method() == Method::GET; }) && request.method == Method::GET;
} }
} }
@ -160,26 +171,53 @@ impl<S> Predicate<S> 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 /// ```rust,ignore
/// use actix_web::server; /// use actix_web::HttpServer;
/// use actix_webfinger::app;
/// ///
/// server::new(|| { /// HttpServer::new(|| {
/// app::<_, MyResolver>(my_state) /// App::new()
/// .service(actix_webfinger::resource::<_, MyResolver>())
/// }) /// })
/// .bind("127.0.0.1:8000")? /// .bind("127.0.0.1:8000")?
/// .run(); /// .start();
/// ``` /// ```
pub fn app<S, R>(state: S) -> App<S> pub fn resource<S, R>() -> Resource
where where
R: Resolver<S> + 'static, R: Resolver<S> + 'static,
S: 'static, S: FromRequest + 'static,
{ {
App::with_state(state).resource("/.well-known/webfinger", |r| { actix_web::web::resource("/.well-known/webfinger")
r.route().filter(WebfingerPredicate).with(R::endpoint) .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<S, R>() -> Resource
where
R: Resolver<S> + '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 /// The error created if the webfinger resource query is malformed
@ -290,7 +328,10 @@ pub struct WebfingerQuery {
/// ///
/// struct MyResolver; /// struct MyResolver;
/// ///
/// impl<S> Resolver<S> for MyResolver { /// impl<S> Resolver<S> for MyResolver
/// where
/// S: FromRequest,
/// {
/// type Error = CustomError; /// type Error = CustomError;
/// ///
/// fn find( /// fn find(
@ -308,19 +349,27 @@ pub struct WebfingerQuery {
/// } /// }
/// } /// }
/// ///
/// fn main() { /// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// server::new(|| { /// let sys = System::new("asonix");
///
/// HttpServer::new(|| {
/// App::new() /// App::new()
/// .resource("/.well-known/webfinger", |r| { /// .data(())
/// r.with(<MyResolver as Resolver<()>>::endpoint) /// .service(resource::<Data<()>, MyResolver>())
/// })
/// }) /// })
/// .bind("127.0.0.1:8000")? /// .bind("127.0.0.1:8000")?
/// .run(); /// .start();
///
/// sys.run()?;
///
/// Ok(())
/// } /// }
/// ``` /// ```
pub trait Resolver<S> { pub trait Resolver<S>
type Error: ResponseError; where
S: FromRequest,
{
type Error: ResponseError + 'static;
fn find( fn find(
account: &str, account: &str,
@ -329,7 +378,7 @@ pub trait Resolver<S> {
) -> Box<dyn Future<Item = Option<Webfinger>, Error = Self::Error>>; ) -> Box<dyn Future<Item = Option<Webfinger>, Error = Self::Error>>;
fn endpoint( fn endpoint(
(query, state): (Query<WebfingerQuery>, State<S>), (query, state): (Query<WebfingerQuery>, S),
) -> Box<dyn Future<Item = HttpResponse, Error = Self::Error>> { ) -> Box<dyn Future<Item = HttpResponse, Error = Self::Error>> {
let WebfingerResource { account, domain } = query.into_inner().resource; let WebfingerResource { account, domain } = query.into_inner().resource;
@ -547,14 +596,19 @@ impl Webfinger {
/// Fetch a webfinger with subject `user` from a given `domain` /// Fetch a webfinger with subject `user` from a given `domain`
/// ///
/// This method takes an `Addr<ClientConnector>` 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) /// connector implemenation (currently with OpenSSL or Rustls)
pub fn fetch( pub fn fetch<T>(
conn: Addr<ClientConnector>, conn: T,
user: &str, user: &str,
domain: &str, domain: &str,
https: bool, https: bool,
) -> Box<dyn Future<Item = Self, Error = actix_web::Error>> { ) -> Box<dyn Future<Item = Self, Error = FetchError>>
where
T: Service<Request = Connect, Error = ConnectError> + Clone + 'static,
<T as Service>::Response: Connection,
<T as Service>::Future: 'static,
{
let url = format!( let url = format!(
"{}://{}/.well-known/webfinger?resource=acct:{}", "{}://{}/.well-known/webfinger?resource=acct:{}",
if https { "https" } else { "http" }, if https { "https" } else { "http" },
@ -562,21 +616,27 @@ impl Webfinger {
user user
); );
let fut = client::get(url) let fut = Client::build()
.with_connector(conn) .connector(conn)
.header("Accept", "application/jrd+json") .header("Accept", "application/jrd+json")
.finish() .finish()
.into_future() .get(url)
.and_then(|r| { .send()
r.send() .map_err(|_| FetchError::Send)
.from_err() .and_then(|mut res| res.json::<Webfinger>().map_err(|_| FetchError::Parse));
.and_then(|res| res.json::<Webfinger>().from_err())
});
Box::new(fut) 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)] #[cfg(test)]
mod tests { mod tests {
use crate::{Webfinger, WebfingerQuery}; use crate::{Webfinger, WebfingerQuery};