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
82 changes: 65 additions & 17 deletions examples/impersonate.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,76 @@
import asyncio
from rnet import Impersonate, Client
from rnet import Impersonate, ImpersonateOS, ImpersonateOption, Client, Response


async def main():
headers = {"foo": "bar", "bar": "foo"}
headers_order = ["accept-encoding", "foo", "bar"]
async def print_response_info(resp: Response):
"""Helper function to print response details

Args:
resp: Response object from the request
"""
async with resp:
print("\n=== Response Information ===")
print(f"Status Code: {resp.status_code}")
print(f"Version: {resp.version}")
print(f"Response URL: {resp.url}")
print(f"Headers: {resp.headers}")
print(f"Encoding: {resp.encoding}")
print(f"Content-Length: {resp.content_length}")
print(f"Remote Address: {resp.remote_addr}")
print(f"Peer Certificate: {resp.peer_certificate()}")
print(f"Content: {await resp.text()}")
print("========================\n")


async def request_firefox():
"""Test request using Firefox browser impersonation

Demonstrates basic browser impersonation with custom header order
"""
print("\n[Testing Firefox Impersonation]")
client = Client(
impersonate=Impersonate.Firefox135,
user_agent="rnet",
headers_order=["accept-encoding", "user-agent", "accept"],
tls_info=True,
default_headers=headers,
headers_order=headers_order,
)
async with await client.get("https://tls.peet.ws/api/all") as resp:
print("Status Code: ", resp.status_code)
print("Version: ", resp.version)
print("Response URL: ", resp.url)
print("Headers: ", resp.headers)
print("Encoding: ", resp.encoding)
print("Content-Length: ", resp.content_length)
print("Remote Address: ", resp.remote_addr)
print("Peer Certificate: ", resp.peer_certificate())
print("Content: ", await resp.text())
resp = await client.get("https://tls.peet.ws/api/all")
await print_response_info(resp)
return client


async def request_chrome_android(client: Client):
"""Test request using Chrome on Android impersonation

Demonstrates advanced impersonation with OS specification

Args:
client: Existing client instance to update
"""
print("\n[Testing Chrome on Android Impersonation]")
client.update(
impersonate=ImpersonateOption(
impersonate=Impersonate.Chrome134,
impersonate_os=ImpersonateOS.Android,
)
)
resp = await client.get("https://tls.peet.ws/api/all")
await print_response_info(resp)


async def main():
"""Main function to run the impersonation examples

Demonstrates different browser impersonation scenarios:
1. Firefox with custom header order
2. Chrome on Android with OS specification
"""
# First test with Firefox
client = await request_firefox()

# Then update and test with Chrome on Android
await request_chrome_android(client)


if __name__ == "__main__":
# Run the async main function
asyncio.run(main())
24 changes: 16 additions & 8 deletions rnet.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,7 @@ class BlockingClient:
# Arguments
* `**kwds` - The parameters to update the client with.

impersonate: typing.Optional[Impersonate]
impersonate_os: typing.Optional[ImpersonateOS]
impersonate_skip_http2: typing.Optional[builtins.bool]
impersonate_skip_headers: typing.Optional[builtins.bool]
impersonate: typing.Optional[typing.Union[Impersonate, ImpersonateOption]]
headers: typing.Optional[typing.Dict[str, bytes]]
headers_order: typing.Optional[typing.List[str]]
proxies: typing.Optional[builtins.list[Proxy]]
Expand Down Expand Up @@ -858,10 +855,7 @@ class Client:
# Arguments
* `**kwds` - The parameters to update the client with.

impersonate: typing.Optional[Impersonate]
impersonate_os: typing.Optional[ImpersonateOS]
impersonate_skip_http2: typing.Optional[builtins.bool]
impersonate_skip_headers: typing.Optional[builtins.bool]
impersonate: typing.Optional[typing.Union[Impersonate, ImpersonateOption]]
headers: typing.Optional[typing.Dict[str, bytes]]
headers_order: typing.Optional[typing.List[str]]
proxies: typing.Optional[builtins.list[Proxy]]
Expand Down Expand Up @@ -1439,6 +1433,20 @@ class HeaderMapKeysIter:
def __iter__(self) -> HeaderMapKeysIter: ...
def __next__(self) -> typing.Optional[typing.Any]: ...

class ImpersonateOption:
r"""
A struct to represent the `ImpersonateOption` class.
"""

def __new__(
cls,
impersonate: Impersonate,
impersonate_os: typing.Optional[ImpersonateOS] = None,
skip_http2: typing.Optional[builtins.bool] = None,
skip_headers: typing.Optional[builtins.bool] = None,
): ...
...

class Message:
r"""
A WebSocket message.
Expand Down
44 changes: 7 additions & 37 deletions src/async_impl/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
dns,
error::Error,
typing::{
Cookie, HeaderMap, ImpersonateOS, Method, SslVerify, TlsVersion,
Cookie, HeaderMap, Method, SslVerify, TlsVersion,
param::{ClientParams, RequestParams, UpdateClientParams, WebSocketParams},
},
};
Expand Down Expand Up @@ -361,10 +361,7 @@ impl Client {
///
/// * `**kwds` - Optional request parameters as a dictionary.
///
/// impersonate: typing.Optional[Impersonate]
/// impersonate_os: typing.Optional[ImpersonateOS]
/// impersonate_skip_http2: typing.Optional[builtins.bool]
/// impersonate_skip_headers: typing.Optional[builtins.bool]
/// impersonate: typing.Optional[typing.Union[Impersonate, ImpersonateOption]]
/// base_url: typing.Optional[str]
/// user_agent: typing.Optional[str]
/// default_headers: typing.Optional[typing.Dict[str, bytes]]
Expand Down Expand Up @@ -426,19 +423,7 @@ impl Client {

// Impersonation options.
if let Some(impersonate) = params.impersonate.take() {
builder = builder.emulation(
rquest_util::EmulationOption::builder()
.emulation(impersonate.into_ffi())
.emulation_os(
params
.impersonate_os
.map(ImpersonateOS::into_ffi)
.unwrap_or_default(),
)
.skip_http2(params.impersonate_skip_http2.unwrap_or(false))
.skip_headers(params.impersonate_skip_headers.unwrap_or(false))
.build(),
);
builder = builder.emulation(impersonate.0);
}

// User agent options.
Expand Down Expand Up @@ -600,7 +585,7 @@ impl Client {

// Network options.
if let Some(proxies) = params.proxies.take() {
for proxy in proxies {
for proxy in proxies.0 {
builder = builder.proxy(proxy);
}
}
Expand Down Expand Up @@ -779,10 +764,7 @@ impl Client {
/// # Arguments
/// * `**kwds` - The parameters to update the client with.
///
/// impersonate: typing.Optional[Impersonate]
/// impersonate_os: typing.Optional[ImpersonateOS]
/// impersonate_skip_http2: typing.Optional[builtins.bool]
/// impersonate_skip_headers: typing.Optional[builtins.bool]
/// impersonate: typing.Optional[typing.Union[Impersonate, ImpersonateOption]]
/// headers: typing.Optional[typing.Dict[str, bytes]]
/// headers_order: typing.Optional[typing.List[str]]
/// proxies: typing.Optional[builtins.list[Proxy]]
Expand All @@ -809,19 +791,7 @@ impl Client {

// Impersonation options.
if let Some(impersonate) = params.impersonate.take() {
update = update.emulation(
rquest_util::EmulationOption::builder()
.emulation(impersonate.into_ffi())
.emulation_os(
params
.impersonate_os
.map(ImpersonateOS::into_ffi)
.unwrap_or_default(),
)
.skip_http2(params.impersonate_skip_http2.unwrap_or(false))
.skip_headers(params.impersonate_skip_headers.unwrap_or(false))
.build(),
);
update = update.emulation(impersonate.0);
}

// Default headers options.
Expand All @@ -838,7 +808,7 @@ impl Client {
);

// Network options.
apply_option!(apply_if_some, update, params.proxies, proxies);
apply_option!(apply_if_some_inner, update, params.proxies, proxies);
apply_option!(
apply_transformed_option,
update,
Expand Down
4 changes: 2 additions & 2 deletions src/async_impl/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ where
);

// Network options.
apply_option!(apply_if_some, builder, params.proxy, proxy);
apply_option!(apply_if_some_inner, builder, params.proxy, proxy);
apply_option!(
apply_transformed_option,
builder,
Expand Down Expand Up @@ -123,7 +123,7 @@ where
apply_option!(apply_if_some, builder, params.body, body);

// Multipart options.
apply_option!(apply_if_some, builder, params.multipart, multipart);
apply_option!(apply_if_some_inner, builder, params.multipart, multipart);

// Send the request.
builder
Expand Down
10 changes: 2 additions & 8 deletions src/blocking/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,10 +351,7 @@ impl BlockingClient {
///
/// * `**kwds` - Optional request parameters as a dictionary.
///
/// impersonate: typing.Optional[Impersonate]
/// impersonate_os: typing.Optional[ImpersonateOS]
/// impersonate_skip_http2: typing.Optional[builtins.bool]
/// impersonate_skip_headers: typing.Optional[builtins.bool]
/// impersonate: typing.Optional[typing.Union[Impersonate, ImpersonateOption]]
/// user_agent: typing.Optional[str]
/// default_headers: typing.Optional[typing.Dict[str, bytes]]
/// headers_order: typing.Optional[typing.List[str]]
Expand Down Expand Up @@ -504,10 +501,7 @@ impl BlockingClient {
/// # Arguments
/// * `**kwds` - The parameters to update the client with.
///
/// impersonate: typing.Optional[Impersonate]
/// impersonate_os: typing.Optional[ImpersonateOS]
/// impersonate_skip_http2: typing.Optional[builtins.bool]
/// impersonate_skip_headers: typing.Optional[builtins.bool]
/// impersonate: typing.Optional[typing.Union[Impersonate, ImpersonateOption]]
/// headers: typing.Optional[typing.Dict[str, bytes]]
/// headers_order: typing.Optional[typing.List[str]]
/// proxies: typing.Optional[builtins.list[Proxy]]
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use pyo3_stub_gen::{define_stub_info_gatherer, derive::*};
use typing::param::{RequestParams, WebSocketParams};
use typing::{
Cookie, HeaderMap, HeaderMapItemsIter, HeaderMapKeysIter, Impersonate, ImpersonateOS,
LookupIpStrategy, Method, Multipart, Part, Proxy, SameSite, SocketAddr, StatusCode, TlsVersion,
Version,
ImpersonateOption, LookupIpStrategy, Method, Multipart, Part, Proxy, SameSite, SocketAddr,
StatusCode, TlsVersion, Version,
};

#[cfg(not(target_env = "msvc"))]
Expand Down Expand Up @@ -325,6 +325,7 @@ fn rnet(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<HeaderMapKeysIter>()?;
m.add_class::<Impersonate>()?;
m.add_class::<ImpersonateOS>()?;
m.add_class::<ImpersonateOption>()?;
m.add_class::<TlsVersion>()?;
m.add_class::<SocketAddr>()?;
m.add_class::<Proxy>()?;
Expand Down
14 changes: 7 additions & 7 deletions src/typing/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@ use pyo3::{FromPyObject, PyAny};
use rquest::Body;

/// The body to use for the request.
pub enum FromPyBody {
pub enum BodyExtractor {
Text(Bytes),
Bytes(Bytes),
SyncStream(SyncStream),
AsyncStream(AsyncStream),
}

impl From<FromPyBody> for Body {
fn from(value: FromPyBody) -> Body {
impl From<BodyExtractor> for Body {
fn from(value: BodyExtractor) -> Body {
match value {
FromPyBody::Text(bytes) | FromPyBody::Bytes(bytes) => Body::from(bytes),
FromPyBody::SyncStream(stream) => Body::wrap_stream(stream),
FromPyBody::AsyncStream(stream) => Body::wrap_stream(stream),
BodyExtractor::Text(bytes) | BodyExtractor::Bytes(bytes) => Body::from(bytes),
BodyExtractor::SyncStream(stream) => Body::wrap_stream(stream),
BodyExtractor::AsyncStream(stream) => Body::wrap_stream(stream),
}
}
}

impl FromPyObject<'_> for FromPyBody {
impl FromPyObject<'_> for BodyExtractor {
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
if let Ok(text) = ob.extract::<PyBackedStr>() {
return Ok(Self::Text(Bytes::from_owner(text)));
Expand Down
7 changes: 3 additions & 4 deletions src/typing/cookie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,9 @@ impl Cookie {
}
}

/// Parse a cookie header from a Python dictionary.
pub struct CookieFromPyDict(pub HeaderValue);
pub struct CookieExtractor(pub HeaderValue);

impl FromPyObject<'_> for CookieFromPyDict {
impl FromPyObject<'_> for CookieExtractor {
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
let dict = ob.downcast::<PyDict>()?;
dict.iter()
Expand All @@ -202,7 +201,7 @@ impl FromPyObject<'_> for CookieFromPyDict {
}

#[cfg(feature = "docs")]
impl PyStubType for CookieFromPyDict {
impl PyStubType for CookieExtractor {
fn type_output() -> TypeInfo {
TypeInfo::with_module("typing.Dict[str, str]", "typing".into())
}
Expand Down
12 changes: 6 additions & 6 deletions src/typing/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,12 @@ impl HeaderMapItemsIter {
}

/// A HTTP header map.
pub struct HeaderMapFromPy(pub header::HeaderMap);
pub struct HeaderMapExtractor(pub header::HeaderMap);

/// A list of header names in order.
pub struct HeadersOrderFromPyList(pub Vec<HeaderName>);
pub struct HeadersOrderExtractor(pub Vec<HeaderName>);

impl FromPyObject<'_> for HeaderMapFromPy {
impl FromPyObject<'_> for HeaderMapExtractor {
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
if let Ok(headers) = ob.downcast::<HeaderMap>() {
return Ok(Self(headers.borrow().0.clone()));
Expand All @@ -188,7 +188,7 @@ impl FromPyObject<'_> for HeaderMapFromPy {
}

#[cfg(feature = "docs")]
impl<'py> IntoPyObject<'py> for HeaderMapFromPy {
impl<'py> IntoPyObject<'py> for HeaderMapExtractor {
type Target = HeaderMap;

type Output = Bound<'py, Self::Target>;
Expand All @@ -201,7 +201,7 @@ impl<'py> IntoPyObject<'py> for HeaderMapFromPy {
}

#[cfg(feature = "docs")]
impl PyStubType for HeaderMapFromPy {
impl PyStubType for HeaderMapExtractor {
fn type_output() -> TypeInfo {
TypeInfo::with_module(
"typing.Union[typing.Dict[str, str], HeaderMap]",
Expand All @@ -210,7 +210,7 @@ impl PyStubType for HeaderMapFromPy {
}
}

impl<'py> FromPyObject<'py> for HeadersOrderFromPyList {
impl<'py> FromPyObject<'py> for HeadersOrderExtractor {
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
let list = ob.downcast::<PyList>()?;
list.iter()
Expand Down
Loading
Loading