Skip to content

Commit

Permalink
Client: 1) add headers, redirects, verify, http1, http2 par…
Browse files Browse the repository at this point in the history
…ams, 2) add get(), post(), head(), put(), delete(), patch() methods.
  • Loading branch information
deedy5 committed Apr 13, 2024
1 parent ad21d2a commit 57602ab
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 21 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Client:
"""Initializes a blocking HTTP client that can impersonate web browsers.
Args:
headers (dict, optional): headers to send with requests. If `impersonate` is set, this will be ignored.
timeout (float, optional): HTTP request timeout in seconds. Default is 30.
proxy (str, optional): Proxy URL for HTTP requests. Example: "socks5://127.0.0.1:9150". Default is None.
impersonate (str, optional): Entity to impersonate. Example: "chrome_123". Default is None.
Expand All @@ -33,14 +34,18 @@ class Client:
Safari: "safari_12","safari_15_3","safari_15_5","safari_15_6_1","safari_16","safari_16_5","safari_17_2_1"
OkHttp: "okhttp_3_9","okhttp_3_11","okhttp_3_13","okhttp_3_14","okhttp_4_9","okhttp_4_10","okhttp_5"
Edge: "edge_99","edge_101","edge_120"
redirects (int, optional): number of redirects. If set to 0|False, no redirects will be followed. Default is 10.
verify (bool, optional): verify SSL certificates. Default is True.
http1 (bool, optional): use only HTTP/1.1. Default is None.
http2 (bool, optional): use only HTTP/2. Default is None.
"""
```
Example:
```python
from pyreqwest_impersonate import Client

client = Client(impersonate="chrome_123")
resp = client.request("GET", "https://tls.peet.ws/api/all")
resp = client.get("https://tls.peet.ws/api/all")
print(resp.text)
print(resp.status_code)
print(resp.url)
Expand Down
133 changes: 113 additions & 20 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use pyo3::exceptions;
use pyo3::prelude::*;
use reqwest_impersonate::header::{HeaderMap, HeaderName, HeaderValue};
use reqwest_impersonate::impersonate::Impersonate;
use reqwest_impersonate::redirect::Policy;
use reqwest_impersonate::Method;
use std::collections::HashMap;
use std::str::FromStr;
Expand Down Expand Up @@ -31,35 +33,100 @@ impl Client {
#[new]
/// Initializes a blocking HTTP client that can impersonate web browsers.
///
/// Args:
/// timeout (float, optional): HTTP request timeout in seconds. Default is 30.
/// proxy (str, optional): Proxy URL for HTTP requests. Example: "socks5://127.0.0.1:9150". Default is None.
/// impersonate (str, optional): Entity to impersonate. Example: "chrome_123". Default is None.
/// Chrome: "chrome_99","chrome_100","chrome_101","chrome_104","chrome_105","chrome_106","chrome_108",
/// "chrome_107","chrome_109","chrome_114","chrome_116","chrome_117","chrome_118","chrome_119",
/// "chrome_120","chrome_123"
/// Safari: "safari_12","safari_15_3","safari_15_5","safari_15_6_1","safari_16","safari_16_5","safari_17_2_1"
/// OkHttp: "okhttp_3_9","okhttp_3_11","okhttp_3_13","okhttp_3_14","okhttp_4_9","okhttp_4_10","okhttp_5"
/// Edge: "edge_99","edge_101","edge_120"
fn new(timeout: Option<f64>, proxy: Option<&str>, impersonate: Option<&str>) -> PyResult<Self> {
/// This function creates a new instance of a blocking HTTP client that can impersonate various web browsers.
/// It allows for customization of headers, proxy settings, timeout, impersonation type, SSL certificate verification,
/// and HTTP version preferences.
///
/// # Arguments
///
/// * `headers` - An optional map of HTTP headers to send with requests. If `impersonate` is set, this will be ignored.
/// * `proxy` - An optional proxy URL for HTTP requests.
/// * `timeout` - An optional timeout for HTTP requests in seconds.
/// * `impersonate` - An optional entity to impersonate. Supported browsers and versions include Chrome, Safari, OkHttp, and Edge.
/// * `redirects` - An optional number of redirects to follow. If set to 0, no redirects will be followed. Default is 10.
/// * `verify` - An optional boolean indicating whether to verify SSL certificates. Default is `true`.
/// * `http1` - An optional boolean indicating whether to use only HTTP/1.1. Default is `false`.
/// * `http2` - An optional boolean indicating whether to use only HTTP/2. Default is `false`.
///
/// # Example
///
/// ```
/// from reqwest_impersonate import Client
///
/// client = Client(
/// headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36"},
/// proxy="http://127.0.0.1:8080",
/// timeout=10,
/// impersonate="chrome_123",
/// redirects=0,
/// verify=False,
/// http1=True,
/// http2=False,
/// )
/// ```
fn new(
headers: Option<HashMap<String, String>>,
proxy: Option<&str>,
timeout: Option<f64>,
impersonate: Option<&str>,
redirects: Option<usize>,
verify: Option<bool>,
http1: Option<bool>,
http2: Option<bool>,
) -> PyResult<Self> {
let mut client_builder = reqwest_impersonate::blocking::Client::builder()
.danger_accept_invalid_certs(true)
.enable_ech_grease(true)
.permute_extensions(true)
.cookie_store(true)
.timeout(timeout.map(Duration::from_secs_f64));

if let Some(headers) = headers {
let mut headers_new = HeaderMap::new();
for (key, value) in headers {
headers_new.insert(
HeaderName::from_bytes(key.as_bytes()).map_err(|_| {
PyErr::new::<exceptions::PyValueError, _>("Invalid header name")
})?,
HeaderValue::from_str(&value).map_err(|_| {
PyErr::new::<exceptions::PyValueError, _>("Invalid header value")
})?,
);
}
client_builder = client_builder.default_headers(headers_new);
}

if let Some(proxy_url) = proxy {
let proxy = reqwest_impersonate::Proxy::all(proxy_url)
.map_err(|_| PyErr::new::<exceptions::PyValueError, _>("Invalid proxy URL"))?;
client_builder = client_builder.proxy(proxy);
}

if let Some(impersonation_type) = impersonate {
let impersonation = Impersonate::from_str(impersonation_type).map_err(|_| {
PyErr::new::<exceptions::PyValueError, _>("Invalid impersonate param")
})?;
client_builder = client_builder.impersonate(impersonation);
}

if let Some(proxy_url) = proxy {
let proxy = reqwest_impersonate::Proxy::all(proxy_url)
.map_err(|_| PyErr::new::<exceptions::PyValueError, _>("Invalid proxy URL"))?;
client_builder = client_builder.proxy(proxy);
match redirects {
Some(0) => client_builder = client_builder.redirect(Policy::none()),
Some(n) => client_builder = client_builder.redirect(Policy::limited(n)),
None => client_builder = client_builder.redirect(Policy::limited(10)), // default to 10 redirects
}

if let Some(verify) = verify {
client_builder = client_builder.danger_accept_invalid_certs(!verify);
}

match (http1, http2) {
(Some(true), Some(true)) => {
return Err(PyErr::new::<exceptions::PyValueError, _>(
"Both http1 and http2 cannot be true",
));
}
(Some(true), _) => client_builder = client_builder.http1_only(),
(_, Some(true)) => client_builder = client_builder.http2_prior_knowledge(),
_ => (),
}

let client = client_builder
Expand All @@ -86,13 +153,11 @@ impl Client {
let method = match method {
"GET" => Ok(Method::GET),
"POST" => Ok(Method::POST),
"PUT" => Ok(Method::PUT),
"DELETE" => Ok(Method::DELETE),
"HEAD" => Ok(Method::HEAD),
"OPTIONS" => Ok(Method::OPTIONS),
"CONNECT" => Ok(Method::CONNECT),
"TRACE" => Ok(Method::TRACE),
"PUT" => Ok(Method::PUT),
"PATCH" => Ok(Method::PATCH),
"DELETE" => Ok(Method::DELETE),
&_ => Err(PyErr::new::<exceptions::PyException, _>(
"Unrecognized HTTP method",
)),
Expand Down Expand Up @@ -129,6 +194,34 @@ impl Client {
url,
})
}

fn get(&self, url: &str, timeout: Option<f64>) -> PyResult<Response> {
self.request("GET", url, timeout)
}

fn post(&self, url: &str, timeout: Option<f64>) -> PyResult<Response> {
self.request("POST", url, timeout)
}

fn head(&self, url: &str, timeout: Option<f64>) -> PyResult<Response> {
self.request("HEAD", url, timeout)
}

fn options(&self, url: &str, timeout: Option<f64>) -> PyResult<Response> {
self.request("OPTIONS", url, timeout)
}

fn put(&self, url: &str, timeout: Option<f64>) -> PyResult<Response> {
self.request("PUT", url, timeout)
}

fn patch(&self, url: &str, timeout: Option<f64>) -> PyResult<Response> {
self.request("PATCH", url, timeout)
}

fn delete(&self, url: &str, timeout: Option<f64>) -> PyResult<Response> {
self.request("DELETE", url, timeout)
}
}

#[pymodule]
Expand Down

0 comments on commit 57602ab

Please sign in to comment.