koonjs 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +549 -0
- package/package.json +22 -13
package/README.md
ADDED
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
# koon
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/koonjs)
|
|
4
|
+
[](https://pypi.org/project/koon/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://github.com/scrape-hub/koon/actions)
|
|
7
|
+
|
|
8
|
+
An HTTP client that impersonates real browsers at the TLS, HTTP/2, and HTTP/3 fingerprint level.
|
|
9
|
+
|
|
10
|
+
Built in Rust on top of BoringSSL with native bindings for **Node.js**, **Python**, **R**, and a **CLI**. Passes Akamai, Cloudflare, and other bot detection systems by reproducing exact browser fingerprints — verified against real browser captures.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
**Node.js**
|
|
15
|
+
```bash
|
|
16
|
+
npm install koonjs
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Python**
|
|
20
|
+
```bash
|
|
21
|
+
pip install koon
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**R**
|
|
25
|
+
```r
|
|
26
|
+
# Install from source (requires Rust toolchain)
|
|
27
|
+
remotes::install_github("scrape-hub/koon", subdir = "crates/r")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**CLI** — download from [Releases](https://github.com/scrape-hub/koon/releases), or:
|
|
31
|
+
```bash
|
|
32
|
+
cargo install koon-cli
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
**Node.js**
|
|
38
|
+
```javascript
|
|
39
|
+
import { Koon } from 'koonjs';
|
|
40
|
+
|
|
41
|
+
const client = new Koon({ browser: 'chrome145' });
|
|
42
|
+
const resp = await client.get('https://httpbin.org/json');
|
|
43
|
+
console.log(resp.ok); // true
|
|
44
|
+
console.log(resp.text()); // body as string
|
|
45
|
+
console.log(resp.json()); // parsed JSON
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Python**
|
|
49
|
+
```python
|
|
50
|
+
from koon import KoonSync
|
|
51
|
+
|
|
52
|
+
client = KoonSync("chrome145")
|
|
53
|
+
resp = client.get("https://httpbin.org/json")
|
|
54
|
+
print(resp.ok) # True
|
|
55
|
+
print(resp.json()) # parsed JSON
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**R**
|
|
59
|
+
```r
|
|
60
|
+
library(koon)
|
|
61
|
+
|
|
62
|
+
client <- Koon$new("chrome145")
|
|
63
|
+
resp <- client$get("https://httpbin.org/json")
|
|
64
|
+
resp$ok # TRUE
|
|
65
|
+
resp$text # body as string
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**CLI**
|
|
69
|
+
```bash
|
|
70
|
+
koon -b chrome145 https://example.com
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Rust**
|
|
74
|
+
```rust
|
|
75
|
+
use koon_core::{Client, Chrome};
|
|
76
|
+
|
|
77
|
+
let client = Client::new(Chrome::v145_windows())?;
|
|
78
|
+
let r = client.get("https://example.com").await?;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## What it does
|
|
82
|
+
|
|
83
|
+
koon reproduces three fingerprint layers that bot detection systems check:
|
|
84
|
+
|
|
85
|
+
| Layer | What's fingerprinted | How koon matches it |
|
|
86
|
+
|-------|---------------------|-------------------|
|
|
87
|
+
| **TLS** | Cipher suites, curves, extensions, ALPN, GREASE, ALPS | BoringSSL with per-browser config (JA3/JA4 verified) |
|
|
88
|
+
| **HTTP/2** | SETTINGS order, pseudo-header order, WINDOW_UPDATE, PRIORITY frames | Forked h2 crate with header ordering API (Akamai hash verified) |
|
|
89
|
+
| **HTTP/3** | QUIC transport params, H3 settings | Quinn + h3 with browser-matching config |
|
|
90
|
+
|
|
91
|
+
All fingerprints are tested against hashes captured from real browsers. 10 integration tests verify JA3N, JA4, and Akamai hashes for Chrome, Firefox, Safari, Edge, and Opera.
|
|
92
|
+
|
|
93
|
+
## Supported browsers
|
|
94
|
+
|
|
95
|
+
| Browser | Versions | Platforms | Profiles |
|
|
96
|
+
|---------|----------|-----------|----------|
|
|
97
|
+
| Chrome | 131 – 145 | Windows, macOS, Linux, Android | 60 |
|
|
98
|
+
| Firefox | 135 – 148 | Windows, macOS, Linux, Android | 56 |
|
|
99
|
+
| Safari | 15.6 – 18.3 | macOS, iOS | 15 |
|
|
100
|
+
| Edge | 131 – 145 | Windows, macOS | 30 |
|
|
101
|
+
| Opera | 124 – 127 | Windows, macOS, Linux | 12 |
|
|
102
|
+
| OkHttp | 4, 5 | Android | 2 |
|
|
103
|
+
|
|
104
|
+
**175 profiles** total. Use `koon --list-browsers` (CLI) to see all profiles.
|
|
105
|
+
|
|
106
|
+
### Profile naming
|
|
107
|
+
|
|
108
|
+
Format: `{browser}{version}{-os}` — all parts except the browser name are optional. Both `chrome145-macos` and `chrome145macos` work (dash is optional).
|
|
109
|
+
|
|
110
|
+
**Desktop browsers with OS variants:**
|
|
111
|
+
|
|
112
|
+
| Browser | Default (Windows) | Windows | macOS | Linux |
|
|
113
|
+
|---------|-------------------|---------|-------|-------|
|
|
114
|
+
| Chrome 145 | `chrome145` | `chrome145-windows` | `chrome145-macos` | `chrome145-linux` |
|
|
115
|
+
| Firefox 148 | `firefox148` | `firefox148-windows` | `firefox148-macos` | `firefox148-linux` |
|
|
116
|
+
| Edge 145 | `edge145` | `edge145-windows` | `edge145-macos` | — |
|
|
117
|
+
| Opera 127 | `opera127` | `opera127-windows` | `opera127-macos` | `opera127-linux` |
|
|
118
|
+
| Safari 18.3 | `safari183` | — | `safari183-macos` | — |
|
|
119
|
+
|
|
120
|
+
**Mobile browsers:**
|
|
121
|
+
|
|
122
|
+
| Browser | Example |
|
|
123
|
+
|---------|---------|
|
|
124
|
+
| Chrome Mobile (Android) | `chrome-mobile145` |
|
|
125
|
+
| Firefox Mobile (Android) | `firefox-mobile148` |
|
|
126
|
+
| Safari Mobile (iOS) | `safari-mobile183` |
|
|
127
|
+
|
|
128
|
+
**OkHttp (Android apps):**
|
|
129
|
+
|
|
130
|
+
| Version | Name |
|
|
131
|
+
|---------|------|
|
|
132
|
+
| OkHttp 4 | `okhttp4` |
|
|
133
|
+
| OkHttp 5 | `okhttp5` |
|
|
134
|
+
|
|
135
|
+
**Shorthand** — omit the version to get the latest:
|
|
136
|
+
|
|
137
|
+
| Shorthand | Resolves to |
|
|
138
|
+
|-----------|-------------|
|
|
139
|
+
| `chrome` | Chrome 145 Windows |
|
|
140
|
+
| `firefox` | Firefox 148 Windows |
|
|
141
|
+
| `safari` | Safari 18.3 macOS |
|
|
142
|
+
| `edge` | Edge 145 Windows |
|
|
143
|
+
| `opera` | Opera 127 Windows |
|
|
144
|
+
| `chrome-mobile` | Chrome Mobile 145 Android |
|
|
145
|
+
| `firefox-mobile` | Firefox Mobile 148 Android |
|
|
146
|
+
| `safari-mobile` | Safari Mobile 18.3 iOS |
|
|
147
|
+
| `okhttp` | OkHttp 5 |
|
|
148
|
+
|
|
149
|
+
## Features
|
|
150
|
+
|
|
151
|
+
- **TLS fingerprint** — cipher list, curves, sigalgs, extension order, GREASE, ALPS, cert compression, delegated credentials
|
|
152
|
+
- **HTTP/2 fingerprint** — SETTINGS order, pseudo-header order, stream dependencies, priority frames, window sizes
|
|
153
|
+
- **HTTP/3 (QUIC)** — Alt-Svc discovery, QUIC transport parameter fingerprinting, H3 connection pooling
|
|
154
|
+
- **Header order preservation** — HTTP/2 (via forked h2) and HTTP/1.1
|
|
155
|
+
- **Encrypted Client Hello** — real ECH from DNS HTTPS records, with GREASE fallback
|
|
156
|
+
- **DNS-over-HTTPS** — Cloudflare and Google resolvers with ECH config discovery
|
|
157
|
+
- **TLS session resumption** — session ticket caching across requests
|
|
158
|
+
- **Cookie jar** — automatic persistence with domain/path/expiry/Secure/HttpOnly/SameSite
|
|
159
|
+
- **Proxy** — HTTP CONNECT and SOCKS5, with H3 fallback to H2 through proxies
|
|
160
|
+
- **MITM proxy server** — local proxy that re-sends all traffic through koon's fingerprinted stack
|
|
161
|
+
- **WebSocket** — `wss://` connections with browser-matching TLS handshake
|
|
162
|
+
- **Streaming responses** — chunked body streaming with async iterator support
|
|
163
|
+
- **Multipart form-data** — file uploads with custom content types
|
|
164
|
+
- **Per-request headers, timeout, and proxy** — override defaults per request without affecting the client
|
|
165
|
+
- **Ergonomic response API** — `ok`, `text()`, `json()`, `header()` on every response
|
|
166
|
+
- **Session persistence** — save/load cookies and TLS session tickets to JSON
|
|
167
|
+
- **Fingerprint randomization** — slight jitter on UA build number, accept-language q-values, H2 window sizes
|
|
168
|
+
- **Response decompression** — gzip, brotli, deflate, zstd (automatic)
|
|
169
|
+
- **Local address binding** — bind outgoing connections to a specific local IP (multi-IP servers, IP rotation)
|
|
170
|
+
- **Connection pooling** — H3 multiplexed + H2 multiplexed + H1.1 keep-alive
|
|
171
|
+
- **Custom redirect hook** — `onRedirect(status, url, headers) → bool` to intercept and stop redirects (captcha detection, geo-block handling)
|
|
172
|
+
- **Automatic retry** — retry on transport errors (connection, TLS, timeout) with automatic proxy rotation
|
|
173
|
+
- **Request hooks** — `onRequest`/`onResponse` observe-only callbacks for logging and debugging
|
|
174
|
+
- **Proxy rotation** — round-robin over multiple proxy URLs, proxy-aware connection pool
|
|
175
|
+
- **Bandwidth tracking** — per-request `bytesSent`/`bytesReceived` + cumulative counters on the client
|
|
176
|
+
- **String body** — `post()`, `put()`, `patch()` accept strings directly (no `Buffer.from()` needed)
|
|
177
|
+
- **User-Agent property** — `client.userAgent` exposes the profile UA for Puppeteer/Playwright sync
|
|
178
|
+
- **Geo-locale matching** — `locale: 'fr-FR'` generates Accept-Language matching proxy geography
|
|
179
|
+
- **Structured errors** — machine-readable `[CODE]` prefix on all errors (TIMEOUT, TLS_ERROR, PROXY_ERROR, etc.)
|
|
180
|
+
- **Connection info** — `resp.tlsResumed` and `resp.connectionReused` for debugging connection behavior
|
|
181
|
+
- **CONNECT proxy headers** — custom headers in the HTTP CONNECT tunnel (session IDs, geo-targeting for Bright Data, Oxylabs)
|
|
182
|
+
- **IPv4/IPv6 toggle** — restrict DNS resolution to a specific IP version
|
|
183
|
+
- **Mobile browser profiles** — Chrome Mobile (Android), Firefox Mobile (Android), Safari Mobile (iOS) with platform-specific TLS/H2 fingerprints
|
|
184
|
+
- **OkHttp profiles** — Android app impersonation (OkHttp 4.x, 5.x) with Conscrypt TLS stack fingerprint
|
|
185
|
+
- **Sync Python API** — `KoonSync` wrapper for all HTTP methods without `asyncio` (WebSocket and streaming remain async-only)
|
|
186
|
+
|
|
187
|
+
## Usage
|
|
188
|
+
|
|
189
|
+
### Node.js
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
import { Koon } from 'koonjs';
|
|
193
|
+
|
|
194
|
+
// Browser profile + options
|
|
195
|
+
const client = new Koon({
|
|
196
|
+
browser: 'chrome145',
|
|
197
|
+
headers: { 'X-Custom': 'value' },
|
|
198
|
+
proxy: 'socks5://127.0.0.1:1080', // optional
|
|
199
|
+
localAddress: '192.168.1.100', // optional: bind to specific IP
|
|
200
|
+
randomize: true, // optional: slight fingerprint jitter
|
|
201
|
+
retries: 3, // optional: retry on transport errors
|
|
202
|
+
locale: 'fr-FR', // optional: Accept-Language for proxy geo
|
|
203
|
+
ipVersion: 4, // optional: force IPv4 DNS resolution
|
|
204
|
+
proxyHeaders: { // optional: CONNECT tunnel headers
|
|
205
|
+
'X-Session-Id': 'abc123',
|
|
206
|
+
},
|
|
207
|
+
onRedirect: (status, url, headers) => {
|
|
208
|
+
return !url.includes('captcha'); // stop if redirect goes to captcha
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// HTTP methods
|
|
213
|
+
const r1 = await client.get('https://httpbin.org/get');
|
|
214
|
+
const r2 = await client.post('https://httpbin.org/post', 'data');
|
|
215
|
+
const r3 = await client.put('https://httpbin.org/put', 'data');
|
|
216
|
+
const r4 = await client.delete('https://httpbin.org/delete');
|
|
217
|
+
const r5 = await client.patch('https://httpbin.org/patch', 'data');
|
|
218
|
+
const r6 = await client.head('https://httpbin.org/get');
|
|
219
|
+
|
|
220
|
+
// User-Agent (useful for Puppeteer/Playwright sync)
|
|
221
|
+
console.log(client.userAgent); // "Mozilla/5.0 ... Chrome/145..."
|
|
222
|
+
|
|
223
|
+
// Response
|
|
224
|
+
console.log(r1.ok); // true (status 2xx)
|
|
225
|
+
console.log(r1.status); // 200
|
|
226
|
+
console.log(r1.text()); // body as string (charset-aware)
|
|
227
|
+
console.log(r1.json()); // parsed JSON
|
|
228
|
+
console.log(r1.contentType); // e.g. "text/html; charset=utf-8"
|
|
229
|
+
console.log(r1.header('content-type')); // case-insensitive header lookup
|
|
230
|
+
console.log(r1.body); // raw Buffer
|
|
231
|
+
console.log(r1.tlsResumed); // TLS session was reused
|
|
232
|
+
console.log(r1.connectionReused); // pooled connection was reused
|
|
233
|
+
console.log(r1.remoteAddress); // remote peer IP, or null for H3
|
|
234
|
+
console.log(r1.bytesSent, r1.bytesReceived); // bandwidth per request
|
|
235
|
+
|
|
236
|
+
// Per-request headers, timeout, and proxy
|
|
237
|
+
const r7 = await client.get('https://httpbin.org/get', {
|
|
238
|
+
headers: { 'Authorization': 'Bearer token' },
|
|
239
|
+
timeout: 5, // 5s timeout for this request only
|
|
240
|
+
proxy: 'http://user:pass@other-proxy:8080', // override proxy for this request
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Cookies persist automatically
|
|
244
|
+
await client.get('https://httpbin.org/cookies/set/name/value');
|
|
245
|
+
const r = await client.get('https://httpbin.org/cookies');
|
|
246
|
+
|
|
247
|
+
// Clear cookies (keeps TLS sessions and connection pool)
|
|
248
|
+
client.clearCookies();
|
|
249
|
+
|
|
250
|
+
// Session save/load
|
|
251
|
+
const session = client.saveSession(); // JSON string
|
|
252
|
+
const client2 = new Koon({ browser: 'chrome145' });
|
|
253
|
+
client2.loadSession(session);
|
|
254
|
+
|
|
255
|
+
// File: save/load to disk
|
|
256
|
+
client.saveSessionToFile('session.json');
|
|
257
|
+
client2.loadSessionFromFile('session.json');
|
|
258
|
+
|
|
259
|
+
// WebSocket
|
|
260
|
+
const ws = await client.websocket('wss://echo.websocket.org');
|
|
261
|
+
await ws.send('hello');
|
|
262
|
+
const msg = await ws.receive(); // { isText: true, data: Buffer }
|
|
263
|
+
await ws.close();
|
|
264
|
+
|
|
265
|
+
// Streaming
|
|
266
|
+
const stream = await client.requestStreaming('GET', 'https://example.com/large');
|
|
267
|
+
console.log(stream.status);
|
|
268
|
+
const body = await stream.collect(); // or iterate with nextChunk()
|
|
269
|
+
|
|
270
|
+
// Multipart upload
|
|
271
|
+
await client.postMultipart('https://httpbin.org/post', [
|
|
272
|
+
{ name: 'field', value: 'text' },
|
|
273
|
+
{ name: 'file', fileData: Buffer.from('...'), filename: 'upload.txt', contentType: 'text/plain' },
|
|
274
|
+
]);
|
|
275
|
+
|
|
276
|
+
// MITM proxy
|
|
277
|
+
import { KoonProxy } from 'koonjs';
|
|
278
|
+
const proxy = await KoonProxy.start({ browser: 'chrome145', listenAddr: '127.0.0.1:8080' });
|
|
279
|
+
console.log(proxy.url); // http://127.0.0.1:8080
|
|
280
|
+
console.log(proxy.caCertPath); // path to CA cert for trust
|
|
281
|
+
await proxy.shutdown();
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Python
|
|
285
|
+
|
|
286
|
+
`KoonSync` provides a blocking API — no `asyncio` needed:
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
from koon import KoonSync
|
|
290
|
+
|
|
291
|
+
# Browser profile + options
|
|
292
|
+
client = KoonSync("chrome145",
|
|
293
|
+
headers={"X-Custom": "value"},
|
|
294
|
+
retries=3, # retry on transport errors
|
|
295
|
+
locale="fr-FR", # Accept-Language for proxy geo
|
|
296
|
+
ip_version=4, # force IPv4 DNS resolution
|
|
297
|
+
proxy_headers={"X-Session-Id": "abc123"}, # CONNECT tunnel headers
|
|
298
|
+
on_redirect=lambda s, u, h: "captcha" not in u,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# HTTP methods
|
|
302
|
+
r = client.get("https://httpbin.org/get")
|
|
303
|
+
r = client.post("https://httpbin.org/post", "data")
|
|
304
|
+
r = client.put("https://httpbin.org/put", "data")
|
|
305
|
+
r = client.delete("https://httpbin.org/delete")
|
|
306
|
+
r = client.patch("https://httpbin.org/patch", "data")
|
|
307
|
+
r = client.head("https://httpbin.org/get")
|
|
308
|
+
|
|
309
|
+
# Response
|
|
310
|
+
print(r.ok) # True (status 2xx)
|
|
311
|
+
print(r.status) # 200
|
|
312
|
+
print(r.text) # body as string (charset-aware)
|
|
313
|
+
print(r.json()) # parsed JSON
|
|
314
|
+
print(r.content_type) # e.g. "text/html; charset=utf-8"
|
|
315
|
+
print(r.header("content-type")) # case-insensitive header lookup
|
|
316
|
+
print(r.tls_resumed) # TLS session was reused
|
|
317
|
+
print(r.connection_reused) # pooled connection was reused
|
|
318
|
+
print(r.bytes_sent, r.bytes_received) # bandwidth per request
|
|
319
|
+
|
|
320
|
+
# Per-request headers, timeout, and proxy
|
|
321
|
+
r = client.get("https://httpbin.org/get",
|
|
322
|
+
headers={"Authorization": "Bearer token"},
|
|
323
|
+
timeout=5, # 5s timeout for this request only
|
|
324
|
+
proxy="http://user:pass@other-proxy:8080", # override proxy for this request
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Cookies persist automatically
|
|
328
|
+
client.get("https://httpbin.org/cookies/set/name/value")
|
|
329
|
+
r = client.get("https://httpbin.org/cookies")
|
|
330
|
+
|
|
331
|
+
# Clear cookies (keeps TLS sessions and connection pool)
|
|
332
|
+
client.clear_cookies()
|
|
333
|
+
|
|
334
|
+
# Session save/load
|
|
335
|
+
session = client.save_session()
|
|
336
|
+
client2 = KoonSync("chrome145")
|
|
337
|
+
client2.load_session(session)
|
|
338
|
+
|
|
339
|
+
# User-Agent (useful for Puppeteer/Playwright sync)
|
|
340
|
+
print(client.user_agent) # "Mozilla/5.0 ... Chrome/145..."
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
For async code, use `Koon` instead — same API, but all request methods are coroutines:
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
from koon import Koon
|
|
347
|
+
|
|
348
|
+
client = Koon("chrome145")
|
|
349
|
+
resp = await client.get("https://httpbin.org/get")
|
|
350
|
+
|
|
351
|
+
# WebSocket (async only)
|
|
352
|
+
ws = await client.websocket("wss://echo.websocket.org")
|
|
353
|
+
await ws.send("hello")
|
|
354
|
+
msg = await ws.receive()
|
|
355
|
+
await ws.close()
|
|
356
|
+
|
|
357
|
+
# Streaming (async only)
|
|
358
|
+
stream = await client.request_streaming("GET", "https://example.com/large")
|
|
359
|
+
body = await stream.collect()
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### R
|
|
363
|
+
|
|
364
|
+
```r
|
|
365
|
+
library(koon)
|
|
366
|
+
|
|
367
|
+
# Browser profile + options
|
|
368
|
+
client <- Koon$new("chrome145", proxy = "socks5://127.0.0.1:1080", randomize = TRUE,
|
|
369
|
+
local_address = "192.168.1.100", retries = 3L,
|
|
370
|
+
locale = "fr-FR", ip_version = 4L,
|
|
371
|
+
proxy_headers = c(`X-Session-Id` = "abc123"),
|
|
372
|
+
on_redirect = function(status, url, headers) !grepl("captcha", url))
|
|
373
|
+
|
|
374
|
+
# HTTP methods (synchronous)
|
|
375
|
+
resp <- client$get("https://httpbin.org/get")
|
|
376
|
+
resp <- client$post("https://httpbin.org/post", "data")
|
|
377
|
+
resp <- client$put("https://httpbin.org/put", "data")
|
|
378
|
+
resp <- client$delete("https://httpbin.org/delete")
|
|
379
|
+
resp <- client$patch("https://httpbin.org/patch", "data")
|
|
380
|
+
resp <- client$head("https://httpbin.org/get")
|
|
381
|
+
|
|
382
|
+
# Response
|
|
383
|
+
resp$ok # TRUE (status 2xx)
|
|
384
|
+
resp$status # 200
|
|
385
|
+
resp$version # "HTTP/2.0"
|
|
386
|
+
resp$text # body as string (charset-aware)
|
|
387
|
+
resp$content_type # e.g. "text/html; charset=utf-8"
|
|
388
|
+
resp$body # raw vector
|
|
389
|
+
resp$headers # data.frame with name + value columns
|
|
390
|
+
|
|
391
|
+
# Parse JSON (via jsonlite)
|
|
392
|
+
data <- jsonlite::fromJSON(resp$text)
|
|
393
|
+
|
|
394
|
+
# Per-request headers
|
|
395
|
+
resp <- client$get("https://httpbin.org/get",
|
|
396
|
+
headers = c(Authorization = "Bearer token")
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Cookies persist automatically
|
|
400
|
+
client$get("https://httpbin.org/cookies/set/name/value")
|
|
401
|
+
resp <- client$get("https://httpbin.org/cookies")
|
|
402
|
+
|
|
403
|
+
# Clear cookies (keeps TLS sessions and connection pool)
|
|
404
|
+
client$clear_cookies()
|
|
405
|
+
|
|
406
|
+
# Session save/load
|
|
407
|
+
json <- client$save_session()
|
|
408
|
+
client2 <- Koon$new("chrome145")
|
|
409
|
+
client2$load_session(json)
|
|
410
|
+
|
|
411
|
+
# Export profile as JSON
|
|
412
|
+
client$export_profile()
|
|
413
|
+
|
|
414
|
+
# List all browsers
|
|
415
|
+
koon_browsers()
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### CLI
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
# GET with browser profile
|
|
422
|
+
koon -b chrome145 https://example.com
|
|
423
|
+
|
|
424
|
+
# POST with body
|
|
425
|
+
koon -b firefox147 -X POST -d '{"key":"value"}' https://httpbin.org/post
|
|
426
|
+
|
|
427
|
+
# Custom headers
|
|
428
|
+
koon -b safari183 -H "Authorization: Bearer token" https://api.example.com
|
|
429
|
+
|
|
430
|
+
# Verbose output (request/response headers)
|
|
431
|
+
koon -b chrome145 -v https://httpbin.org/get
|
|
432
|
+
|
|
433
|
+
# JSON output
|
|
434
|
+
koon -b chrome145 --json https://httpbin.org/get
|
|
435
|
+
|
|
436
|
+
# Save response to file
|
|
437
|
+
koon -b chrome145 -o page.html https://example.com
|
|
438
|
+
|
|
439
|
+
# Proxy
|
|
440
|
+
koon -b chrome145 --proxy socks5://127.0.0.1:1080 https://example.com
|
|
441
|
+
|
|
442
|
+
# Session persistence
|
|
443
|
+
koon -b chrome145 --save-session session.json https://example.com/login
|
|
444
|
+
koon -b chrome145 --load-session session.json https://example.com/dashboard
|
|
445
|
+
|
|
446
|
+
# DNS-over-HTTPS
|
|
447
|
+
koon -b chrome145 --doh cloudflare https://example.com
|
|
448
|
+
|
|
449
|
+
# OS-specific user-agent
|
|
450
|
+
koon -b chrome145-macos https://example.com
|
|
451
|
+
|
|
452
|
+
# Fingerprint randomization
|
|
453
|
+
koon -b chrome145 --randomize https://example.com
|
|
454
|
+
|
|
455
|
+
# List all browser profiles
|
|
456
|
+
koon --list-browsers
|
|
457
|
+
|
|
458
|
+
# Export profile as JSON
|
|
459
|
+
koon --export-profile chrome145
|
|
460
|
+
|
|
461
|
+
# Start MITM proxy
|
|
462
|
+
koon proxy --browser chrome145 --listen 127.0.0.1:8080
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Rust
|
|
466
|
+
|
|
467
|
+
```toml
|
|
468
|
+
[dependencies]
|
|
469
|
+
koon-core = { git = "https://github.com/scrape-hub/koon.git" }
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
```rust
|
|
473
|
+
use koon_core::{BrowserProfile, Client};
|
|
474
|
+
use koon_core::profile::Chrome;
|
|
475
|
+
|
|
476
|
+
#[tokio::main]
|
|
477
|
+
async fn main() -> Result<(), koon_core::Error> {
|
|
478
|
+
// From a specific profile constructor
|
|
479
|
+
let client = Client::new(Chrome::v145_windows())?;
|
|
480
|
+
|
|
481
|
+
// Or with builder for full control
|
|
482
|
+
let profile = BrowserProfile::resolve("chrome145")?;
|
|
483
|
+
let client = Client::builder(profile)
|
|
484
|
+
.max_retries(3)
|
|
485
|
+
.locale("fr-FR")
|
|
486
|
+
.ip_version(koon_core::IpVersion::V4)
|
|
487
|
+
.on_redirect(|status, url, _headers| {
|
|
488
|
+
!url.contains("captcha")
|
|
489
|
+
})
|
|
490
|
+
.build()?;
|
|
491
|
+
|
|
492
|
+
let r = client.get("https://example.com").await?;
|
|
493
|
+
println!("{} {} ({} bytes)", r.status, r.version, r.body.len());
|
|
494
|
+
|
|
495
|
+
// Clear cookies without resetting TLS/pool
|
|
496
|
+
client.clear_cookies();
|
|
497
|
+
|
|
498
|
+
Ok(())
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## Architecture
|
|
503
|
+
|
|
504
|
+
```
|
|
505
|
+
koon-core Rust library — TLS, HTTP/2, HTTP/3, profiles, proxy
|
|
506
|
+
koon-node Node.js native addon via napi-rs
|
|
507
|
+
koon-python Python extension via PyO3 + maturin
|
|
508
|
+
koon-r R package via extendr
|
|
509
|
+
koon-cli Command-line interface via clap
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Key dependencies:
|
|
513
|
+
- [boring2](https://github.com/0x676e67/boring2) — BoringSSL Rust bindings
|
|
514
|
+
- [http2](https://github.com/scrape-hub/http2) (fork) — HTTP/2 with header field ordering
|
|
515
|
+
- [quinn](https://github.com/quinn-rs/quinn) + [h3](https://github.com/hyperium/h3) — QUIC / HTTP/3
|
|
516
|
+
- [napi-rs](https://napi.rs) — Rust to Node.js bridge
|
|
517
|
+
- [PyO3](https://pyo3.rs) + [maturin](https://github.com/PyO3/maturin) — Rust to Python bridge
|
|
518
|
+
- [extendr](https://extendr.rs) — Rust to R bridge
|
|
519
|
+
|
|
520
|
+
## Building from source
|
|
521
|
+
|
|
522
|
+
Only needed if you want to build koon yourself instead of using the published packages.
|
|
523
|
+
|
|
524
|
+
**Requirements:**
|
|
525
|
+
- Rust 1.85+
|
|
526
|
+
- CMake
|
|
527
|
+
- NASM (Windows only, for BoringSSL assembly)
|
|
528
|
+
- C compiler — MSVC (Windows), GCC or Clang (Linux/macOS)
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
# Core library
|
|
532
|
+
cargo build --release -p koon-core
|
|
533
|
+
|
|
534
|
+
# Node.js addon
|
|
535
|
+
cargo build --release -p koon-node
|
|
536
|
+
|
|
537
|
+
# Python package
|
|
538
|
+
cd crates/python && pip install -e .
|
|
539
|
+
|
|
540
|
+
# R package
|
|
541
|
+
cd crates/r && Rscript -e "rextendr::document(); devtools::install()"
|
|
542
|
+
|
|
543
|
+
# CLI binary
|
|
544
|
+
cargo build --release -p koon-cli
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
## License
|
|
548
|
+
|
|
549
|
+
[MIT](LICENSE)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koonjs",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "Browser-impersonating HTTP client
|
|
3
|
+
"version": "0.6.3",
|
|
4
|
+
"description": "Browser-impersonating HTTP client — TLS, HTTP/2, HTTP/3 fingerprint spoofing. Passes Akamai & Cloudflare. 175 browser profiles.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -18,13 +18,14 @@
|
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"index.js",
|
|
21
|
-
"index.d.ts"
|
|
21
|
+
"index.d.ts",
|
|
22
|
+
"README.md"
|
|
22
23
|
],
|
|
23
24
|
"optionalDependencies": {
|
|
24
|
-
"@koonjs/win32-x64-msvc": "0.6.
|
|
25
|
-
"@koonjs/linux-x64-gnu": "0.6.
|
|
26
|
-
"@koonjs/darwin-arm64": "0.6.
|
|
27
|
-
"@koonjs/darwin-x64": "0.6.
|
|
25
|
+
"@koonjs/win32-x64-msvc": "0.6.3",
|
|
26
|
+
"@koonjs/linux-x64-gnu": "0.6.3",
|
|
27
|
+
"@koonjs/darwin-arm64": "0.6.3",
|
|
28
|
+
"@koonjs/darwin-x64": "0.6.3"
|
|
28
29
|
},
|
|
29
30
|
"scripts": {
|
|
30
31
|
"build": "cargo build --release -p koon-node && node -e \"require('fs').copyFileSync(require('path').resolve(__dirname,'../../target/release/koon_node.dll'), require('path').resolve(__dirname,'koon.win32-x64-msvc.node'))\"",
|
|
@@ -33,15 +34,23 @@
|
|
|
33
34
|
"test": "node ../../test_node.cjs"
|
|
34
35
|
},
|
|
35
36
|
"keywords": [
|
|
36
|
-
"tls",
|
|
37
|
-
"
|
|
38
|
-
"http2",
|
|
39
|
-
"browser",
|
|
40
|
-
"impersonation",
|
|
37
|
+
"tls-fingerprint",
|
|
38
|
+
"browser-impersonation",
|
|
39
|
+
"http2-fingerprint",
|
|
41
40
|
"anti-bot",
|
|
41
|
+
"web-scraping",
|
|
42
42
|
"akamai",
|
|
43
43
|
"cloudflare",
|
|
44
44
|
"ja3",
|
|
45
|
-
"ja4"
|
|
45
|
+
"ja4",
|
|
46
|
+
"http-client",
|
|
47
|
+
"proxy",
|
|
48
|
+
"scraping",
|
|
49
|
+
"stealth",
|
|
50
|
+
"curl-impersonate",
|
|
51
|
+
"boringssl",
|
|
52
|
+
"http3",
|
|
53
|
+
"quic",
|
|
54
|
+
"websocket"
|
|
46
55
|
]
|
|
47
56
|
}
|