-
Notifications
You must be signed in to change notification settings - Fork 45
Expand file tree
/
Copy pathlib.rs
More file actions
218 lines (202 loc) · 7.85 KB
/
Copy pathlib.rs
File metadata and controls
218 lines (202 loc) · 7.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
use std::time::Duration;
use impit::{errors::ImpitError, impit::Impit, request::RequestOptions};
use napi::{bindgen_prelude::ObjectFinalize, Env};
use napi_derive::napi;
mod abortable_stream;
mod cookies;
mod impit_builder;
mod request;
mod response;
use self::response::ImpitResponse;
use cookies::NodeCookieJar;
use impit_builder::ImpitOptions;
use request::{HttpMethod, RequestInit};
/// The main class of the `impit` package
///
/// This class is the primary interface for making HTTP requests.
/// It provides methods to configure the Impit instance and to perform requests.
///
/// @example
/// ```ts
/// import { Impit } from 'impit';
///
/// const impit = new Impit();
/// const response = await impit.fetch('https://example.com');
/// console.log(await response.text());
/// ```
///
/// One `Impit` instance represents a single (possibly impersonated) user agent.
///
/// Note that all the requests made by this instance will share the same configuration,
/// resources (e.g. cookie jar and connection pool), and other settings.
#[napi(js_name = "Impit", custom_finalize)]
pub struct ImpitWrapper {
inner: Impit<NodeCookieJar>,
}
impl ObjectFinalize for ImpitWrapper {
fn finalize(self, env: Env) -> napi::Result<()> {
env.adjust_external_memory(-500 * 1024)?;
Ok(())
}
}
#[napi]
impl ImpitWrapper {
/// Creates a new `Impit` instance with the given options.
///
/// The `options` parameter allows you to customize the behavior of the Impit instance.
/// If no options are provided, default settings will be used.
///
/// @example
/// ```ts
/// import { Impit } from 'impit';
///
/// const impit = new Impit({
/// timeout: 5e3, // Set a default timeout of 5000
/// headers: {
/// 'Authorization: 'Bearer <token>',
/// },
/// browser: 'chrome',
/// });
/// ```
#[napi(constructor)]
pub fn new(env: &Env, options: Option<ImpitOptions>) -> Result<Self, napi::Error> {
let config = options.unwrap_or_default().into_builder()?;
let _ = env.adjust_external_memory(500 * 1024);
// `quinn` for h3 requires existing async runtime.
// This runs the `config.build` function in the napi-managed tokio runtime which remains available
// throughout the lifetime of the `ImpitWrapper` instance.
napi::bindgen_prelude::block_on(async {
Ok(Self {
inner: config.build().map_err(|e| {
napi::Error::new(
napi::Status::GenericFailure,
format!("Failed to build Impit instance: {e}"),
)
})?,
})
})
}
#[napi(js_name = "getMultipartBoundary", skip_typescript)]
pub fn get_multipart_boundary(&self) -> String {
self.inner.generate_multipart_boundary()
}
#[napi(ts_args_type = "resource: string | URL | Request, init?: RequestInit")]
/// Fetch a URL with the given options.
///
/// This method performs an HTTP request to the specified URL using the provided options.
/// It returns a promise that resolves to an {@link ImpitResponse} object containing the response data.
///
/// This method is designed to be API-compatible with the {@link https://developer.mozilla.org/en-US/docs/Web/API/fetch | Fetch API `fetch`} global method.
///
/// @example
/// ```ts
/// import { Impit } from 'impit';
///
/// const impit = new Impit();
/// const response = await impit.fetch('https://example.com', {
/// method: 'GET',
/// headers: {
/// 'Accept': 'application/json'
/// },
/// timeout: 5e3,
/// });
/// ```
pub async fn fetch(
&self,
url: String,
request_init: Option<RequestInit>,
) -> Result<ImpitResponse, napi::Error> {
let request_options = Some(RequestOptions {
headers: request_init
.as_ref()
.and_then(|init| init.headers.as_ref())
.cloned()
.unwrap_or_default(),
timeout: request_init
.as_ref()
.and_then(|init| init.timeout)
.map(|timeout| Some(Duration::from_millis(timeout.into()))),
http3_prior_knowledge: request_init
.as_ref()
.and_then(|init| init.force_http3)
.unwrap_or_default(),
});
let method = request_init
.as_ref()
.and_then(|init| init.method.to_owned())
.unwrap_or_default();
let body = request_init
.and_then(|init| init.body)
.map(|array| array.to_vec().into());
let response = if matches!(method, HttpMethod::Get | HttpMethod::Head) && body.is_some() {
Err(ImpitError::BindingPassthroughError(
"GET/HEAD methods don't support passing a request body".to_string(),
))
} else {
// Match the HTTP method and execute the corresponding request
match method {
HttpMethod::Get => self.inner.get(url, body, request_options).await,
HttpMethod::Head => self.inner.head(url, body, request_options).await,
HttpMethod::Post => self.inner.post(url, body, request_options).await,
HttpMethod::Put => self.inner.put(url, body, request_options).await,
HttpMethod::Delete => self.inner.delete(url, body, request_options).await,
HttpMethod::Patch => self.inner.patch(url, body, request_options).await,
HttpMethod::Options => self.inner.options(url, body, request_options).await,
HttpMethod::Trace => self.inner.trace(url, body, request_options).await,
}
};
match response {
Ok(response) => ImpitResponse::try_from_response(response),
Err(err) => {
let status = match err {
ImpitError::UrlMissingHostnameError(_) => napi::Status::InvalidArg,
ImpitError::UrlProtocolError(_) => napi::Status::InvalidArg,
ImpitError::UrlParsingError(_) => napi::Status::InvalidArg,
ImpitError::InvalidMethod(_) => napi::Status::InvalidArg,
_ => napi::Status::GenericFailure,
};
let error_code = match &err {
ImpitError::HTTPError => "HTTPError",
ImpitError::RequestError => "RequestError",
ImpitError::TransportError => "TransportError",
ImpitError::TimeoutException(_) => "TimeoutException",
ImpitError::ConnectTimeout => "ConnectTimeout",
ImpitError::ReadTimeout => "ReadTimeout",
ImpitError::WriteTimeout => "WriteTimeout",
ImpitError::PoolTimeout => "PoolTimeout",
ImpitError::NetworkError => "NetworkError",
ImpitError::ConnectError(_) => "ConnectError",
ImpitError::ReadError => "ReadError",
ImpitError::WriteError => "WriteError",
ImpitError::CloseError => "CloseError",
ImpitError::ProtocolError => "ProtocolError",
ImpitError::LocalProtocolError => "LocalProtocolError",
ImpitError::RemoteProtocolError => "RemoteProtocolError",
ImpitError::ProxyError(_) => "ProxyError",
ImpitError::ProxyTunnelError(_) => "ProxyTunnelError",
ImpitError::ProxyAuthRequired => "ProxyAuthRequired",
ImpitError::UnsupportedProtocol => "UnsupportedProtocol",
ImpitError::DecodingError => "DecodingError",
ImpitError::TooManyRedirects(_) => "TooManyRedirects",
ImpitError::HTTPStatusError(_) => "HTTPStatusError",
ImpitError::InvalidURL
| ImpitError::UrlParsingError(_)
| ImpitError::UrlMissingHostnameError(_)
| ImpitError::UrlProtocolError(_) => "InvalidURL",
ImpitError::CookieConflict => "CookieConflict",
ImpitError::StreamError => "StreamError",
ImpitError::StreamConsumed => "StreamConsumed",
ImpitError::ResponseNotRead => "ResponseNotRead",
ImpitError::RequestNotRead => "RequestNotRead",
ImpitError::StreamClosed => "StreamClosed",
ImpitError::InvalidHeaderName(_) | ImpitError::InvalidHeaderValue(_) => {
"LocalProtocolError"
}
_ => "HTTPError",
};
let reason = format!("{error_code}: {err}");
Err(napi::Error::new(status, reason))
}
}
}
}