mirror of
https://github.com/Hopiu/lychee.git
synced 2026-05-17 10:11:07 +00:00
Merge branch 'master' of github.com:lycheeverse/lychee
This commit is contained in:
commit
175342baf4
7 changed files with 68 additions and 25 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1579,6 +1579,7 @@ version = "0.7.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cached",
|
"cached",
|
||||||
"check-if-email-exists",
|
"check-if-email-exists",
|
||||||
|
"deadpool",
|
||||||
"doc-comment",
|
"doc-comment",
|
||||||
"fast_chemail",
|
"fast_chemail",
|
||||||
"glob",
|
"glob",
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ async fn main() -> Result<()> {
|
||||||
.build()
|
.build()
|
||||||
.client()?;
|
.client()?;
|
||||||
|
|
||||||
let response = client.check("https://example.org").await?;
|
let response = client.check("http://example.org").await?;
|
||||||
dbg!(&response);
|
dbg!(&response);
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use lychee_lib::{ClientBuilder, Input, Request, Result, Uri};
|
use lychee_lib::{ClientBuilder, ClientPool, Input, Request, Result, Uri};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
|
@ -9,7 +9,7 @@ const CONCURRENT_REQUESTS: usize = 4;
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
// These channels are used to send requests and receive responses to and
|
// These channels are used to send requests and receive responses to and
|
||||||
// from the lychee client pool
|
// from the lychee client pool
|
||||||
let (send_req, mut recv_req) = mpsc::channel(CONCURRENT_REQUESTS);
|
let (send_req, recv_req) = mpsc::channel(CONCURRENT_REQUESTS);
|
||||||
let (send_resp, mut recv_resp) = mpsc::channel(CONCURRENT_REQUESTS);
|
let (send_resp, mut recv_resp) = mpsc::channel(CONCURRENT_REQUESTS);
|
||||||
|
|
||||||
// Add as many requests as you like
|
// Add as many requests as you like
|
||||||
|
|
@ -29,17 +29,13 @@ async fn main() -> Result<()> {
|
||||||
// Create a default lychee client
|
// Create a default lychee client
|
||||||
let client = ClientBuilder::default().client()?;
|
let client = ClientBuilder::default().client()?;
|
||||||
|
|
||||||
|
// Create a pool with four lychee clients
|
||||||
|
let clients = vec![client; CONCURRENT_REQUESTS];
|
||||||
|
let mut clients = ClientPool::new(send_resp, recv_req, clients);
|
||||||
|
|
||||||
// Handle requests in a client pool
|
// Handle requests in a client pool
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some(req) = recv_req.recv().await {
|
clients.listen().await;
|
||||||
// Client::check() may fail only because Request::try_from() may fail
|
|
||||||
// here request is already Request, so it never fails
|
|
||||||
let resp = client.check(req).await.unwrap();
|
|
||||||
send_resp
|
|
||||||
.send(resp)
|
|
||||||
.await
|
|
||||||
.expect("Cannot send response to channel");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Finally, listen to incoming responses from lychee
|
// Finally, listen to incoming responses from lychee
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ use std::{collections::HashSet, fs, str::FromStr};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use headers::HeaderMapExt;
|
use headers::HeaderMapExt;
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use lychee_lib::{ClientBuilder, Collector, Input, Request, Response};
|
use lychee_lib::{ClientBuilder, ClientPool, Collector, Input, Request, Response};
|
||||||
use openssl_sys as _; // required for vendored-openssl feature
|
use openssl_sys as _; // required for vendored-openssl feature
|
||||||
use regex::RegexSet;
|
use regex::RegexSet;
|
||||||
use ring as _; // required for apple silicon
|
use ring as _; // required for apple silicon
|
||||||
|
|
@ -205,7 +205,7 @@ async fn run(cfg: &Config, inputs: Vec<Input>) -> Result<i32> {
|
||||||
.require_https(cfg.require_https)
|
.require_https(cfg.require_https)
|
||||||
.build()
|
.build()
|
||||||
.client()
|
.client()
|
||||||
.map_err(|e| anyhow!(e))?;
|
.map_err(|e| anyhow!("Failed to create request client: {}", e))?;
|
||||||
|
|
||||||
let links = Collector::new(cfg.base.clone(), cfg.skip_missing, max_concurrency)
|
let links = Collector::new(cfg.base.clone(), cfg.skip_missing, max_concurrency)
|
||||||
.collect_links(&inputs)
|
.collect_links(&inputs)
|
||||||
|
|
@ -228,7 +228,7 @@ async fn run(cfg: &Config, inputs: Vec<Input>) -> Result<i32> {
|
||||||
Some(bar)
|
Some(bar)
|
||||||
};
|
};
|
||||||
|
|
||||||
let (send_req, mut recv_req) = mpsc::channel(max_concurrency);
|
let (send_req, recv_req) = mpsc::channel(max_concurrency);
|
||||||
let (send_resp, mut recv_resp) = mpsc::channel(max_concurrency);
|
let (send_resp, mut recv_resp) = mpsc::channel(max_concurrency);
|
||||||
|
|
||||||
let mut stats = ResponseStats::new();
|
let mut stats = ResponseStats::new();
|
||||||
|
|
@ -245,15 +245,9 @@ async fn run(cfg: &Config, inputs: Vec<Input>) -> Result<i32> {
|
||||||
|
|
||||||
// Start receiving requests
|
// Start receiving requests
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some(req) = recv_req.recv().await {
|
let clients = vec![client; max_concurrency];
|
||||||
// `Client::check()` may fail only because `Request::try_from()` may
|
let mut clients = ClientPool::new(send_resp, recv_req, clients);
|
||||||
// fail. Here `req` is already a valid `Request`, so it never fails.
|
clients.listen().await;
|
||||||
let resp = client.check(req).await.unwrap();
|
|
||||||
send_resp
|
|
||||||
.send(resp)
|
|
||||||
.await
|
|
||||||
.expect("Cannot send response to channel");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
while let Some(response) = recv_resp.recv().await {
|
while let Some(response) = recv_resp.recv().await {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ version = "0.7.2"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
check-if-email-exists = "0.8.25"
|
check-if-email-exists = "0.8.25"
|
||||||
|
deadpool = "0.7.0"
|
||||||
fast_chemail = "0.9.6"
|
fast_chemail = "0.9.6"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
html5ever = "0.25.1"
|
html5ever = "0.25.1"
|
||||||
|
|
|
||||||
49
lychee-lib/src/client_pool.rs
Normal file
49
lychee-lib/src/client_pool.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
use client::Client;
|
||||||
|
use deadpool::unmanaged::Pool;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
use crate::{client, types};
|
||||||
|
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
/// Manages a channel for incoming requests
|
||||||
|
/// and a pool of lychee clients to handle them
|
||||||
|
///
|
||||||
|
/// Note: Although `reqwest` has its own pool,
|
||||||
|
/// it only works for connections to the same host, so
|
||||||
|
/// a single client can still be blocked until a request is done.
|
||||||
|
pub struct ClientPool {
|
||||||
|
tx: mpsc::Sender<types::Response>,
|
||||||
|
rx: mpsc::Receiver<types::Request>,
|
||||||
|
pool: deadpool::unmanaged::Pool<client::Client>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientPool {
|
||||||
|
#[must_use]
|
||||||
|
/// Creates a new client pool
|
||||||
|
pub fn new(
|
||||||
|
tx: mpsc::Sender<types::Response>,
|
||||||
|
rx: mpsc::Receiver<types::Request>,
|
||||||
|
clients: Vec<Client>,
|
||||||
|
) -> Self {
|
||||||
|
let pool = Pool::from(clients);
|
||||||
|
ClientPool { tx, rx, pool }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
/// Start listening for incoming requests and send each of them
|
||||||
|
/// asynchronously to a client from the pool
|
||||||
|
pub async fn listen(&mut self) {
|
||||||
|
while let Some(req) = self.rx.recv().await {
|
||||||
|
let client = self.pool.get().await;
|
||||||
|
let tx = self.tx.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
// Client::check() may fail only because Request::try_from() may fail
|
||||||
|
// here request is already Request, so it never fails
|
||||||
|
let resp = client.check(req).await.unwrap();
|
||||||
|
tx.send(resp)
|
||||||
|
.await
|
||||||
|
.expect("Cannot send response to channel");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -47,6 +47,7 @@
|
||||||
doc_comment::doctest!("../../README.md");
|
doc_comment::doctest!("../../README.md");
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
|
mod client_pool;
|
||||||
/// A pool of clients, to handle concurrent checks
|
/// A pool of clients, to handle concurrent checks
|
||||||
pub mod collector;
|
pub mod collector;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
|
@ -72,7 +73,8 @@ use ring as _; // required for apple silicon
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
client::{check, Client, ClientBuilder},
|
client::{check, ClientBuilder},
|
||||||
|
client_pool::ClientPool,
|
||||||
collector::Collector,
|
collector::Collector,
|
||||||
filter::{Excludes, Filter, Includes},
|
filter::{Excludes, Filter, Includes},
|
||||||
types::{Base, ErrorKind, Input, Request, Response, ResponseBody, Result, Status, Uri},
|
types::{Base, ErrorKind, Input, Request, Response, ResponseBody, Result, Status, Uri},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue