Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions impit-node/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,17 @@ fn await_promise<
scope.spawn(move || match tokio::runtime::Runtime::new() {
Ok(runtime) => {
runtime.block_on(async {
let result = tsfn.call_async(args).await.unwrap().await;

let _ = tx.send(result);
match tsfn.call_async(args).await {
Ok(result) => {
let _ = tx.send(result.await);
}
Err(e) => {
let _ = tx.send(Err(napi::Error::new(
napi::Status::GenericFailure,
format!("[impit] failed to retrieve cookies from the external cookie store: {e}"),
)));
}
}
});
}
Err(e) => {
Expand Down Expand Up @@ -158,13 +166,11 @@ impl NodeCookieJar {

let mut set_cookie = set_cookie_js_method
.build_threadsafe_function::<(std::string::String, std::string::String)>()
.build_callback(|ctx| Ok(ctx.value))
.unwrap();
.build_callback(|ctx| Ok(ctx.value))?;

let mut get_cookies = get_cookie_js_method
.build_threadsafe_function::<std::string::String>()
.build_callback(|ctx| Ok(ctx.value))
.unwrap();
.build_callback(|ctx| Ok(ctx.value))?;

// Unless the `ThreadsafeFunction` is unreferenced, the Node.JS application will hang on exit
// https://nodejs.github.io/node-addon-examples/special-topics/thread-safe-functions/#q-my-application-isnt-exiting-correctly-it-just-hangs
Expand Down
10 changes: 9 additions & 1 deletion impit-node/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,15 @@ impl<'env> ImpitResponse {
)?;
}

Ok(this.get(INNER_RESPONSE_PROPERTY_NAME)?.unwrap())
this
.get(INNER_RESPONSE_PROPERTY_NAME)
.transpose()
.ok_or_else(|| {
napi::Error::new(
napi::Status::GenericFailure,
"fatal: Couldn't get cached response stream".to_string(),
)
})?
Comment thread
barjin marked this conversation as resolved.
}

#[napi(ts_return_type = "string")]
Expand Down
2 changes: 1 addition & 1 deletion impit-python/src/async_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ impl AsyncClient {
builder.with_cookie_store(PythonCookieJar::new(py, cookie_jar.into()))
}
(None, Some(cookies)) => {
builder.with_cookie_store(PythonCookieJar::from_httpx_cookies(py, cookies.into()))
builder.with_cookie_store(PythonCookieJar::from_httpx_cookies(py, cookies.into())?)
}
(None, None) => builder,
};
Expand Down
7 changes: 4 additions & 3 deletions impit-python/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,10 @@ impl Client {
(Some(cookie_jar), None) => {
builder.with_cookie_store(PythonCookieJar::new(py, cookie_jar.into()))
}
(None, Some(cookies)) => {
builder.with_cookie_store(PythonCookieJar::from_httpx_cookies(py, cookies.into()))
}
(None, Some(cookies)) => builder.with_cookie_store(
PythonCookieJar::from_httpx_cookies(py, cookies.into())
.map_err(|_e| ImpitPyError(ImpitError::CookieConflict))?,
),
(None, None) => builder,
};

Expand Down
59 changes: 30 additions & 29 deletions impit-python/src/cookies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,59 +23,61 @@ impl CookieStore for PythonCookieJar {
let cookie = std::str::from_utf8(header_value.as_bytes())
.map_err(cookie::ParseError::from)
.and_then(Cookie::parse)
.unwrap();
.unwrap_or(Cookie::new("<cookie-name>", "<cookie-value>"));
Comment thread
barjin marked this conversation as resolved.

let kwargs = PyDict::new(py);

kwargs.set_item("name", cookie.name()).unwrap();
kwargs.set_item("value", cookie.value()).unwrap();
kwargs.set_item("name", cookie.name()).unwrap_or_default();
kwargs.set_item("value", cookie.value()).unwrap_or_default();
Comment thread
barjin marked this conversation as resolved.
kwargs
.set_item("path", cookie.path().unwrap_or(""))
.unwrap();
.unwrap_or_default();
kwargs
.set_item("secure", cookie.secure().unwrap_or(false))
.unwrap();
.unwrap_or_default();
kwargs
.set_item(
"domain",
cookie
.domain()
.unwrap_or_else(|| url.host_str().unwrap_or_default()),
)
.unwrap();
kwargs.set_item("comment", None::<&str>).unwrap();
kwargs.set_item("comment_url", None::<&str>).unwrap();
kwargs.set_item("port", None::<&str>).unwrap();
kwargs.set_item("port_specified", false).unwrap();
.unwrap_or_default();
kwargs.set_item("comment", None::<&str>).unwrap_or_default();
kwargs
.set_item("comment_url", None::<&str>)
.unwrap_or_default();
kwargs.set_item("port", None::<&str>).unwrap_or_default();
kwargs.set_item("port_specified", false).unwrap_or_default();
kwargs
.set_item("path_specified", cookie.path().is_some())
.unwrap();
.unwrap_or_default();
kwargs
.set_item(
"discard",
cookie.max_age().is_none() && cookie.expires().is_none(),
)
.unwrap();
.unwrap_or_default();
kwargs
.set_item("domain_specified", cookie.domain().is_some())
.unwrap();
.unwrap_or_default();
kwargs
.set_item(
"domain_initial_dot",
cookie.domain().map(|d| d.starts_with('.')),
)
.unwrap();
.unwrap_or_default();
kwargs
.set_item(
"expires",
cookie.expires_datetime().map(|f| f.unix_timestamp()),
)
.unwrap();
kwargs.set_item("version", 0).unwrap();
.unwrap_or_default();
kwargs.set_item("version", 0).unwrap_or_default();

let rest = PyDict::new(py);
if let Some(http_only) = cookie.http_only() {
rest.set_item("HttpOnly", http_only).unwrap();
rest.set_item("HttpOnly", http_only).unwrap_or_default();
}

if let Some(same_site) = cookie.same_site() {
Expand All @@ -84,10 +86,10 @@ impl CookieStore for PythonCookieJar {
cookie::SameSite::Lax => "Lax",
cookie::SameSite::None => "None",
};
rest.set_item("SameSite", same_site_str).unwrap();
rest.set_item("SameSite", same_site_str).unwrap_or_default();
}

kwargs.set_item("rest", rest).unwrap();
kwargs.set_item("rest", rest).unwrap_or_default();

let py_cookie = self.cookie_constructor.call(py, (), Some(&kwargs)).unwrap();

Expand All @@ -110,18 +112,15 @@ impl CookieStore for PythonCookieJar {

let domain = py_cookie
.getattr("domain")
.unwrap()
.extract::<String>()
.unwrap();
.and_then(|attr| attr.extract::<String>())
.unwrap_or_default();
let path = py_cookie
.getattr("path")
.unwrap()
.extract::<String>()
.unwrap();
.and_then(|attr| attr.extract::<String>())
.unwrap_or_default();
let secure = py_cookie
.getattr("secure")
.unwrap()
.extract::<bool>()
.and_then(|attr| attr.extract::<bool>())
.unwrap_or_default();

if !domain.is_empty() && !url.host_str().unwrap_or_default().contains(&domain) {
Expand Down Expand Up @@ -187,7 +186,9 @@ impl PythonCookieJar {
}
}

pub fn from_httpx_cookies(py: Python<'_>, cookies: Py<PyAny>) -> Self {
PythonCookieJar::new(py, cookies.getattr(py, "jar").unwrap())
pub fn from_httpx_cookies(py: Python<'_>, cookies: Py<PyAny>) -> PyResult<Self> {
cookies
.getattr(py, "jar")
.map(|jar| PythonCookieJar::new(py, jar))
}
}
64 changes: 36 additions & 28 deletions impit/src/impit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use tokio::sync::RwLock;

use log::debug;
use reqwest::{cookie::CookieStore, header::HeaderMap, Method, Response, Version};
use std::{fmt::Debug, net::IpAddr, str::FromStr, sync::Arc, time::Duration};
use std::{fmt::Debug, net::IpAddr, sync::Arc, time::Duration};
use url::Url;

use crate::{
Expand Down Expand Up @@ -100,7 +100,7 @@ impl<CookieStoreImpl: CookieStore + 'static> Default for ImpitBuilder<CookieStor
browser: None,
ignore_tls_errors: false,
vanilla_fallback: true,
proxy_url: String::from_str("").unwrap(),
proxy_url: String::new(),
request_timeout: Duration::from_secs(30),
max_http_version: Version::HTTP_2,
redirect: RedirectBehavior::FollowRedirect(10),
Expand Down Expand Up @@ -325,7 +325,11 @@ impl<CookieStoreImpl: CookieStore + 'static> Impit<CookieStoreImpl> {
if engine_guard.is_none() {
*engine_guard = Some(H3Engine::init().await);
}
engine_guard.as_ref().unwrap().host_supports_h3(host).await

match engine_guard.as_ref() {
None => false,
Some(engine) => engine.host_supports_h3(host).await,
}
}
}

Expand All @@ -343,7 +347,7 @@ impl<CookieStoreImpl: CookieStore + 'static> Impit<CookieStoreImpl> {
}

let parsed_url = self.parse_url(url.clone())?;
let host = parsed_url.host_str().unwrap().to_string();
let host = parsed_url.host_str().unwrap_or_default().to_string();

let h3 = options.http3_prior_knowledge || self.should_use_h3(&host).await;

Expand All @@ -357,7 +361,7 @@ impl<CookieStoreImpl: CookieStore + 'static> Impit<CookieStoreImpl> {

let client = if h3 {
debug!("Using QUIC for request to {url}");
self.h3_client.as_ref().unwrap()
self.h3_client.as_ref().unwrap_or(&self.base_client)
} else {
debug!("{url} doesn't seem to have HTTP3 support");
&self.base_client
Expand All @@ -384,36 +388,40 @@ impl<CookieStoreImpl: CookieStore + 'static> Impit<CookieStoreImpl> {

let response = request.send().await;

if response.is_err() {
let max_redirects = match self.config.redirect {
RedirectBehavior::FollowRedirect(max) => max,
RedirectBehavior::ManualRedirect => 0,
};

return Err(ImpitError::from(
response.err().unwrap(),
ErrorContext {
timeout: options.timeout.unwrap_or(self.config.request_timeout),
max_redirects,
method: method.to_string(),
protocol: parsed_url.scheme().to_string(),
url: url.clone(),
},
));
}

let response = response.unwrap();
let response = match response {
Ok(resp) => resp,
Err(err) => {
let max_redirects = match self.config.redirect {
RedirectBehavior::FollowRedirect(max) => max,
RedirectBehavior::ManualRedirect => 0,
};

return Err(ImpitError::from(
err,
ErrorContext {
timeout: options.timeout.unwrap_or(self.config.request_timeout),
max_redirects,
method: method.to_string(),
protocol: parsed_url.scheme().to_string(),
url: url.clone(),
},
));
}
};

if !h3 {
let engine_guard = self.h3_engine.read().await;
if let Some(h3_engine) = engine_guard.as_ref() {
h3_engine.set_h3_support(&host, false).await;

if let Some(alt_svc) = response.headers().get("Alt-Svc") {
let alt_svc = alt_svc.to_str().unwrap();
if alt_svc.contains("h3") {
debug!("{host} supports HTTP/3 (alt-svc header), adding to Alt-Svc cache");
h3_engine.set_h3_support(&host, true).await;
if let Ok(alt_svc_str) = alt_svc.to_str() {
if alt_svc_str.contains("h3") {
debug!(
"{host} supports HTTP/3 (alt-svc header), adding to Alt-Svc cache"
);
h3_engine.set_h3_support(&host, true).await;
}
}
}
}
Expand Down
Loading