CycleTLS
If you have a API change or feature request feel free to open an Issue
🚀 Features
- High-performance Built-in goroutine pool used for handling asynchronous requests
- Custom header ordering via fhttp
- Proxy support | Socks4, Socks5, Socks5h
- Ja3 Token configuration
- HTTP/3 and QUIC support
- WebSocket client
- Server-Sent Events (SSE)
- Connection reuse
- JA4 fingerprinting
Table of contents
Dependencies
node ^v18.0
golang ^v1.21x
Installation
Node Js
$ npm install cycletls
Golang
$ go get github.com/Danny-Dasilva/CycleTLS/cycletls
Usage
Example CycleTLS Request for Typescript and Javascript
You can run this test in tests/simple.test.ts
const initCycleTLS = require('cycletls');
// Typescript: import initCycleTLS from 'cycletls';
(async () => {
// Initiate CycleTLS
const cycleTLS = await initCycleTLS();
// Send request
const response = await cycleTLS('https://ja3er.com/json', {
body: '',
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0',
proxy: 'http://username:password@hostname.com:443'
}, 'get');
// Parse response as JSON
const data = await response.json();
console.log(data);
// Cleanly exit CycleTLS
await cycleTLS.exit();
})();
JA4R (Raw) TLS Fingerprinting
Important: Pass
ja4r
to configure the TLS ClientHello. JA4 (hash) is a report-only value; configuring with a JA4 hash will not change your fingerprint.
JA4R is the raw format of JA4 fingerprinting that allows explicit configuration of cipher suites, extensions, and signature algorithms:
JavaScript Example
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
// Chrome JA4R fingerprint (raw format)
const response = await cycleTLS('https://tls.peet.ws/api/all', {
ja4r: 't13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0000,0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601'
});
const data = await response.json();
console.log('JA4:', data.tls.ja4);
console.log('JA4_r:', data.tls.ja4_r);
console.log('TLS Version:', data.tls.tls_version_negotiated);
await cycleTLS.exit();
})();
Golang JA4R Example
package main
import (
"log"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
client := cycletls.Init(cycletls.WithRawBytes())
defer client.Close()
// Chrome JA4R fingerprint (raw format)
response, err := client.Do("https://tls.peet.ws/api/all", cycletls.Options{
Ja4r: "t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0000,0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601",
UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
}, "GET")
if err != nil {
log.Fatal(err)
}
log.Println("Response with JA4R:", response.Status)
}
HTTP/2 Fingerprinting
HTTP/2 fingerprinting allows you to mimic specific browser HTTP/2 implementations:
JavaScript Example
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
// Firefox HTTP/2 fingerprint
const response = await cycleTLS('https://tls.peet.ws/api/all', {
http2Fingerprint: '1:65536;2:0;4:131072;5:16384|12517377|0|m,p,a,s',
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0'
});
const data = await response.json();
console.log('HTTP/2 Fingerprint:', data.http2.akamai_fingerprint);
console.log('Settings:', data.http2.sent_frames[0].settings);
await cycleTLS.exit();
})();
Golang HTTP/2 Example
package main
import (
"log"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
client := cycletls.Init()
defer client.Close()
// Firefox HTTP/2 fingerprint
response, err := client.Do("https://tls.peet.ws/api/all", cycletls.Options{
HTTP2Fingerprint: "1:65536;2:0;4:131072;5:16384|12517377|0|m,p,a,s",
UserAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0",
}, "GET")
if err != nil {
log.Fatal(err)
}
log.Println("Response with HTTP/2 fingerprint:", response.Status)
}
Common Browser HTTP/2 Fingerprints
Browser | HTTP/2 Fingerprint | Description | |||
---|---|---|---|---|---|
Firefox | `1:65536;2:0;4:131072;5:16384\ | 12517377\ | 0\ | m,p,a,s` | Smaller window size, MPAS priority |
Chrome | `1:65536;2:0;4:6291456;6:262144\ | 15663105\ | 0\ | m,a,s,p` | Larger window size, MASP priority |
Combined Fingerprinting Example
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
// Complete Chrome browser fingerprint with JA4R
const response = await cycleTLS('https://tls.peet.ws/api/all', {
ja4r: 't13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0000,0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601',
http2Fingerprint: '1:65536;2:0;4:131072;5:16384|12517377|0|m,p,a,s',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0'
});
const data = await response.json();
console.log('Complete fingerprint applied successfully');
console.log('JA4:', data.tls.ja4);
console.log('HTTP/2:', data.http2.akamai_fingerprint);
await cycleTLS.exit();
})();
Streaming Responses (Axios-style)
CycleTLS supports axios-compatible streaming responses for real-time data processing:
Basic Streaming Example
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
// Get streaming response
const response = await cycleTLS.get('https://httpbin.org/stream/3', {
headers: { Authorization: `Bearer your_token_here` },
responseType: 'stream',
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0'
});
const stream = response.data;
stream.on('data', data => {
console.log('Received chunk:', data.toString());
});
stream.on('end', () => {
console.log("stream done");
await cycleTLS.exit();
});
stream.on('error', (error) => {
console.error('Stream error:', error);
await cycleTLS.exit();
});
})();
Advanced Streaming with Error Handling
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
try {
const response = await cycleTLS.get('https://httpbin.org/drip?numbytes=100&duration=2', {
responseType: 'stream',
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0',
});
console.log('Status:', response.status);
console.log('Headers:', response.headers);
const chunks = [];
response.data.on('data', (chunk) => {
chunks.push(chunk);
console.log(`Received ${chunk.length} bytes`);
});
response.data.on('end', () => {
console.log('Stream complete');
const fullData = Buffer.concat(chunks);
console.log('Total received:', fullData.length, 'bytes');
await cycleTLS.exit();
});
response.data.on('error', (error) => {
console.error('Stream error:', error);
await cycleTLS.exit();
});
} catch (error) {
console.error('Request failed:', error);
await cycleTLS.exit();
}
})();
Non-Streaming Responses (Default Behavior)
For non-streaming responses, CycleTLS works exactly as before:
// These return buffered responses (existing behavior)
const jsonResponse = await cycleTLS.get('https://httpbin.org/json', {
responseType: 'json' // or omit for default JSON parsing
});
const jsonData = await jsonResponse.json();
console.log(jsonData); // Parsed JSON object
const textResponse = await cycleTLS.get('https://httpbin.org/html', {
responseType: 'text'
});
const textData = await textResponse.text();
console.log(textData); // String content
Example CycleTLS Request for Golang
package main
import (
"log"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
client := cycletls.Init()
defer client.Close()
response, err := client.Do("https://ja3er.com/json", cycletls.Options{
Body: "",
Ja3: "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0",
UserAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
EnableConnectionReuse: true, // Enable connection reuse for better performance
}, "GET")
if err != nil {
log.Print("Request Failed: " + err.Error())
}
log.Println(response)
}
Example using your own custom http.Client
go
import (
"github.com/Danny-Dasilva/CycleTLS/cycletls"
http "github.com/Danny-Dasilva/fhttp" // note this is a drop-in replacement for net/http
)
func main() {
ja3 := "771,52393-52392-52244-52243-49195-49199-49196-49200-49171-49172-156-157-47-53-10,65281-0-23-35-13-5-18-16-30032-11-10,29-23-24,0"
ua := "Chrome Version 57.0.2987.110 (64-bit) Linux"
cycleClient := &http.Client{
Transport: cycletls.NewTransport(ja3, ua),
}
resp, err := cycleClient.Get("https://tls.peet.ws/")
...
}
Performance Enhancement: Raw Bytes Option
The default Init()
method provides the standard v1 API with chan Response
. For performance-critical applications that can handle raw bytes, use the WithRawBytes()
option:
package main
import (
"encoding/json"
"fmt"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
// Use WithRawBytes() option for performance enhancement
client := cycletls.Init(cycletls.WithRawBytes())
defer client.Close()
// Queue a request
go func() {
client.Queue("https://ja3er.com/json", cycletls.Options{
Ja3: "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0",
UserAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
}, "GET")
}()
// Performance pattern: receive raw bytes from RespChanV2
select {
case responseBytes := <-client.RespChanV2:
var response cycletls.Response
json.Unmarshal(responseBytes, &response)
fmt.Printf("Status: %d\n", response.Status)
fmt.Printf("Body: %s\n", response.Body)
// Alternative: still supports v1 pattern via RespChan
case response := <-client.RespChan:
fmt.Printf("Status: %d\n", response.Status)
fmt.Printf("Body: %s\n", response.Body)
}
}
Note: Use Init()
for standard compatibility with chan Response
. Use Init(cycletls.WithRawBytes())
when you need the performance benefits of handling raw []byte
responses directly.
Creating an instance
In order to create a cycleTLS
instance, you can run the following:
JavaScript
// The initCycleTLS function spawns a Golang process that handles all requests concurrently via goroutine loops.
const initCycleTLS = require('cycletls');
// import initCycleTLS from 'cycletls';
// Async/Await method
const cycleTLS = await initCycleTLS();
// With optional configuration
const cycleTLS = await initCycleTLS({ port: 9118, timeout: 30000 });
// .then method
initCycleTLS().then((cycleTLS) => {});
Golang
import (
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
//The `Init` function initializes golang channels to process requests.
client := cycletls.Init()
CycleTLS Alias Methods
The following methods exist in CycleTLS
cycleTLS(url, [config])
cycleTLS.get(url, [config])
cycleTLS.delete(url, [config])
cycleTLS.head(url, [config])
cycleTLS.options(url, [config])
cycleTLS.post(url, [config])
cycleTLS.put(url, config)
cycleTLS.patch(url, [config])
Url is not optional, config is optional
CycleTLS Request Config
{
// URL for the request (required if not specified as an argument)
url: "https://example.com"
// Method for the request ("head" | "get" | "post" | "put" | "delete" | "trace" | "options" | "connect" | "patch")
method: "get" // Default method
// Custom headers to send
headers: { "Authorization": "Bearer someexampletoken" }
// Custom cookies to send
Cookies: [{
"name": "key",
"value": "val",
"path": "/docs",
"domain": "google.com",
"expires": "Mon, 02-Jan-2022 15:04:05 EST"
"maxAge": 90,
"secure": false,
"httpOnly": true,
"sameSite": "Lax"
}],
// Body to send with request (must be a string - cannot pass an object)
body: '',
// JA3 token to send with request
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
// JA4R token for enhanced fingerprinting (raw format)
ja4r: 't13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0000,0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601',
// User agent for request
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0',
// Proxy to send request through (supports http, socks4, socks5, socks5h)
proxy: 'http://username:password@hostname.com:443',
// Amount of seconds before request timeout (default: 7)
timeout: 2,
// Toggle if CycleTLS should follow redirects
disableRedirect: true,
// Custom header order to send with request (This value will overwrite default header order)
headerOrder: ["cache-control", "connection", "host"],
// Toggle if CycleTLS should skip verify certificate (If InsecureSkipVerify is true, TLS accepts any certificate presented by the server and any host name in that certificate.)
insecureSkipVerify: false
// Forces CycleTLS to do a http1 handshake
forceHTTP1: false
// Forces HTTP/3 protocol
forceHTTP3: false
// Enable connection reuse across requests
enableConnectionReuse: true
// HTTP/2 fingerprint
http2Fingerprint: '1:65536;4:131072;5:16384|12517377|3:0:0:201,5:0:0:101,7:0:0:1,9:0:7:1,11:0:3:1,13:0:0:241|m,p,a,s'
// QUIC fingerprint for HTTP/3
quicFingerprint: '16030106f2010006ee03039a2b98d81139db0e128ea09eff...'
// JA4H HTTP client fingerprint
ja4h: 'ge11_73a4f1e_8b3fce7'
}
Response Decompression
CycleTLS automatically handles response decompression for compressed content. No additional configuration is needed.
Supported Compression Formats
gzip
- Automatically decompresseddeflate
- Automatically decompressedbrotli
- Automatically decompressed
JavaScript Decompression Example
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
// CycleTLS automatically handles compressed responses
const response = await cycleTLS('https://httpbin.org/gzip', {
headers: {
'Accept-Encoding': 'gzip, deflate, br' // Optional - CycleTLS sets this automatically
}
});
// Response is automatically decompressed
const data = await response.json();
console.log('Decompressed data:', data);
await cycleTLS.exit();
})();
Golang Decompression Example
package main
import (
"log"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
client := cycletls.Init()
defer client.Close()
// CycleTLS automatically handles compressed responses
response, err := client.Do("https://httpbin.org/gzip", cycletls.Options{
Headers: map[string]string{
"Accept-Encoding": "gzip, deflate, br", // Optional - set automatically
},
}, "GET")
if err != nil {
log.Fatal(err)
}
// Response body is automatically decompressed
log.Println("Decompressed response:", response.Body)
// Parse as JSON if needed
jsonData := response.JSONBody()
log.Println("Parsed JSON:", jsonData)
}
Note: Decompression happens automatically based on the Content-Encoding
header. You don't need to manually decompress responses.
Timeout and Error Handling
CycleTLS provides comprehensive timeout handling and error responses for failed requests.
Timeout Configuration
// JavaScript timeout example
const response = await cycleTLS('https://httpbin.org/delay/10', {
timeout: 5, // 5 seconds timeout
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0'
});
// Golang timeout example
response, err := client.Do("https://httpbin.org/delay/10", cycletls.Options{
Timeout: 5, // 5 seconds timeout
UserAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
}, "GET")
Timeout Error Response
When a request times out, CycleTLS returns a response with:
- Status Code:
408
(Request Timeout) - Body: Contains error message describing the timeout
- Error: JavaScript will have the response object, Go will have
err != nil
JavaScript Timeout Error Handling
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
try {
const response = await cycleTLS('https://httpbin.org/delay/10', {
timeout: 2, // Will timeout after 2 seconds
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0'
});
if (response.status === 408) {
console.log('Request timed out:', response.body);
} else {
const data = await response.json();
console.log('Success:', data);
}
} catch (error) {
console.error('Request failed:', error);
} finally {
await cycleTLS.exit();
}
})();
Golang Timeout Error Handling
package main
import (
"log"
"strings"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
client := cycletls.Init()
defer client.Close()
response, err := client.Do("https://httpbin.org/delay/10", cycletls.Options{
Timeout: 2, // Will timeout after 2 seconds
}, "GET")
if err != nil {
log.Printf("Request failed: %v", err)
return
}
// Check for timeout response
if response.Status == 408 {
log.Printf("Request timed out: %s", response.Body)
return
}
// Check for other error conditions
if strings.Contains(response.Body, "timeout") {
log.Printf("Timeout detected in response: %s", response.Body)
return
}
// Success case
log.Printf("Request succeeded: %d", response.Status)
}
Common Error Status Codes
- 408: Request timeout
- 502: Bad gateway (proxy/connection issues)
- 503: Service unavailable
- 0: Connection failed (network errors)
Proxy Support
CycleTLS supports multiple proxy protocols for routing requests through intermediary servers.
Supported Proxy Types
- HTTP Proxy:
http://proxy.example.com:8080
- HTTPS Proxy:
https://proxy.example.com:8080
- SOCKS4:
socks4://proxy.example.com:1080
- SOCKS5:
socks5://proxy.example.com:1080
- SOCKS5h:
socks5h://proxy.example.com:1080
(hostname resolution through proxy)
JavaScript Proxy Examples
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
// HTTP Proxy with authentication
const httpResponse = await cycleTLS('https://httpbin.org/ip', {
proxy: 'http://username:password@proxy.example.com:8080'
});
// SOCKS5 Proxy
const socksResponse = await cycleTLS('https://httpbin.org/ip', {
proxy: 'socks5://proxy.example.com:1080'
});
// SOCKS5h (hostname resolution through proxy)
const socks5hResponse = await cycleTLS('https://httpbin.org/ip', {
proxy: 'socks5h://proxy.example.com:1080'
});
console.log('HTTP Proxy IP:', await httpResponse.json());
console.log('SOCKS5 IP:', await socksResponse.json());
await cycleTLS.exit();
})();
Golang Proxy Examples
package main
import (
"log"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
client := cycletls.Init()
defer client.Close()
// HTTP Proxy with authentication
httpResponse, err := client.Do("https://httpbin.org/ip", cycletls.Options{
Proxy: "http://username:password@proxy.example.com:8080",
}, "GET")
if err != nil {
log.Printf("HTTP proxy request failed: %v", err)
} else {
log.Printf("HTTP Proxy Response: %s", httpResponse.Body)
}
// SOCKS4 Proxy
socks4Response, err := client.Do("https://httpbin.org/ip", cycletls.Options{
Proxy: "socks4://proxy.example.com:1080",
}, "GET")
if err != nil {
log.Printf("SOCKS4 proxy request failed: %v", err)
} else {
log.Printf("SOCKS4 Response: %s", socks4Response.Body)
}
// SOCKS5 Proxy
socks5Response, err := client.Do("https://httpbin.org/ip", cycletls.Options{
Proxy: "socks5://proxy.example.com:1080",
}, "GET")
if err != nil {
log.Printf("SOCKS5 proxy request failed: %v", err)
} else {
log.Printf("SOCKS5 Response: %s", socks5Response.Body)
}
// SOCKS5h (hostname resolved through proxy)
socks5hResponse, err := client.Do("https://httpbin.org/ip", cycletls.Options{
Proxy: "socks5h://proxy.example.com:1080",
}, "GET")
if err != nil {
log.Printf("SOCKS5h proxy request failed: %v", err)
} else {
log.Printf("SOCKS5h Response: %s", socks5hResponse.Body)
}
}
Proxy Error Handling
// Check for proxy connection errors
response, err := client.Do("https://example.com", cycletls.Options{
Proxy: "socks5://proxy.example.com:1080",
Timeout: 10,
}, "GET")
if err != nil {
log.Printf("Proxy connection failed: %v", err)
return
}
// Check for proxy authentication errors
if response.Status == 407 {
log.Printf("Proxy authentication required")
return
}
// Check for proxy server errors
if response.Status == 502 {
log.Printf("Bad gateway - proxy server error")
return
}
Note: SOCKS5h resolves hostnames through the proxy server, providing better privacy and allowing access to internal networks through the proxy.
CycleTLS Response Schema
{
// Status code returned from server (Number)
status: 200,
// Body returned from the server (String)
body: "",
// Headers returned from the server (Object)
headers: {
"some": "header",
...
},
// FinalUrl returned from the server (String). This field is useful when redirection is active.
finalUrl: "https://final.url/"
}
Multiple Requests Example for Typescript and Javascript
If CycleTLS is being used by in a JavaScript environment, CycleTLS will spawn a Golang process to handle requests. This Golang process handles requests concurrently
in a worker pool. Due to this, CycleTLS returns response objects as soon as they are made available
(in other terms, CycleTLS processes requests as they are received, but responses are returned asynchronously so they will NOT be returned in the order requested)
If you are using CycleTLS in JavaScript, it is necessary to exit out of the instance to prevent zombie processes. The example below shows one way to approach cleanly exiting CycleTLS if you need to process multiple requests (note: keep in mind that calling the exit()
function will kill any requests in progress). If your workflow requires requests running the entire time the process runs, modules such as exit-hook could serve as an alternative solution to cleanly exiting CycleTLS.
const initCycleTLS = require("cycletls");
// Typescript: import initCycleTLS from 'cycletls';
// Defining JA3 token and user agent
const ja3 = "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0";
const userAgent = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0";
// Defining multiple requests
const requestDict = {
"https://httpbin.org/user-agent": {
ja3: ja3,
userAgent: userAgent,
},
"http://httpbin.org/post": {
body: '{"field":"POST-VAL"}',
method: "POST",
},
"http://httpbin.org/cookies": {
cookies: [
{
name: "example1",
value: "aaaaaaa",
expires: "Mon, 02-Jan-2022 15:04:05 EST",
},
],
},
};
// Anonymous async function
(async () => {
// Initiate CycleTLS
const cycleTLS = await initCycleTLS();
// Create promises for all requests
const promises = Object.entries(requestDict).map(async ([url, params]) => {
const response = await cycleTLS(
url, {
body: params.body ?? "",
ja3: params.ja3 ?? ja3,
userAgent: params.userAgent ?? userAgent,
headers: params.headers,
cookies: params.cookies,
}, params.method ?? "GET");
// Parse response based on content type
const data = await response.json();
console.log(url, data);
return { url, data };
});
// Wait for all requests to complete
await Promise.all(promises);
// Cleanly exit CycleTLS
await cycleTLS.exit();
})();
Multiple Requests Example for Golang
The general expectation for golang packages is to expect the user to implement a worker pool or any other form of goroutine/asynchronous processing. This package includes a built in Queue method that leverages a worker pool/channels for long running asynchronous requests against a set of urls.
package main
import (
"log"
cycletls "github.com/Danny-Dasilva/CycleTLS/cycletls"
)
// Static variables
var (
ja3 = "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0"
userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36"
)
// RequestConfig holds the configuration for each request.
type RequestConfig struct {
URL string
Method string
Options cycletls.Options
}
func main() {
client := cycletls.Init(true) // Initialize with worker pool
// Define the requests
requests := []RequestConfig{
{
URL: "http://httpbin.org/delay/4",
Method: "GET",
Options: cycletls.Options{
Ja3: ja3,
UserAgent: userAgent,
},
},
{
URL: "http://httpbin.org/post",
Method: "POST",
Options: cycletls.Options{
Body: `{"field":"POST-VAL"}`,
Ja3: ja3,
UserAgent: userAgent,
},
},
{
URL: "http://httpbin.org/cookies",
Method: "GET",
Options: cycletls.Options{
Ja3: ja3,
UserAgent: userAgent,
Cookies: []cycletls.Cookie{
{
Name: "example1",
Value: "aaaaaaa",
},
},
},
},
}
// Queue the requests
for _, req := range requests {
client.Queue(req.URL, req.Options, req.Method)
}
// Asynchronously read responses as soon as they are available
// They will return as soon as they are processed
// e.g. Delay 3 will be returned last
for i := 0; i < len(requests); i++ {
response := <-client.RespChan
log.Println("Response:", response)
}
// Close the client
client.Close()
}
Dev Setup
If you would like to compile CycleTLS on your own, use the following commands:
Set module-aware mode
go env -w GO111MODULE=off
Install golang dependencies
go get github.com/Danny-Dasilva/CycleTLS/cycletls
install npm packages (this command handles the above)
npm install
To recompile index.ts in the src folder
npm run build
To recompile Golang files in the golang folder
All
npm run build:go
Windows
npm run build:go:windows:amd64
Linux
npm run build:go:linux:amd64
Mac
npm run build:go:mac:arm64
You can view the available compile options within the package.json
Questions
How do I set Cookies
js
const initCycleTLS = require("cycletls");
(async () => {
// Initiate cycleTLS
const cycleTLS = await initCycleTLS();
const response = await cycleTLS("https://httpbin.org/cookies", {
cookies: {
cookie1: "value1",
cookie2: "value2",
},
});
const data = await response.json();
console.log(data);
/* Expected
{
"cookies": {
"cookie1": "value1",
"cookie2": "value2"
}
}
*/
await cycleTLS.exit();
})();
In this simple example you can set the cookie name
and value
within an object
### Javascript Complex Cookie Configuration
If you wish to have more fine grained control over cookie parameters you have access to the full underlying Go struct
here are the following values you can set
ts
export interface Cookie {
name: string;
value: string;
path?: string;
domain?: string;
expires?: string;
rawExpires?: string;
maxAge?: number;
secure?: boolean;
httpOnly?: boolean;
sameSite?: string;
unparsed?: string;
}
you can use them in a request as follows
js
const initCycleTLS = require("cycletls");
(async () => {
// Initiate cycleTLS
const cycleTLS = await initCycleTLS();
const complexCookies = [
{
name: "cookie1",
value: "value1",
domain: "httpbin.org",
},
{
name: "cookie2",
value: "value2",
domain: "httpbin.org",
},
];
const response = await cycleTLS("https://httpbin.org/cookies", {
cookies: complexCookies,
});
const data = await response.json();
console.log(data);
/* Expected
{
"cookies": {
"cookie1": "value1",
"cookie2": "value2"
}
}
*/
await cycleTLS.exit();
})();
### Golang Configure Cookies
golang
package main
import (
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
resp, err := client.Do("https://httpbin.org/cookies", cycletls.Options{
Body: "",
Ja3: "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0",
UserAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
Cookies: []cycletls.Cookie{{Name: "cookie1", Value: "value1"},
{Name: "cookie2", Value: "value2"}},
}, "GET")
if err != nil {
log.Print("Request Failed: " + err.Error())
}
log.Println(resp.Body)
/* Expected
{
"cookies": {
"cookie1": "value1",
"cookie2": "value2"
}
}
*/
// Alternatively if you want access to values within a map
log.Println(resp.JSONBody())
/* Expected
map[cookies:map[cookie1:value1 cookie2:value2]]
*/
}
Feel free to open an Issue with a feature request for specific file type support.
How do I use CookieJar in CycleTLS?
js
const initCycleTLS = require("cycletls");
const tough = require("tough-cookie");
const Cookie = tough.Cookie;
(async () => {
// Initiate cycleTLS and CookieJar
const cycleTLS = await initCycleTLS();
const cookieJar = new tough.CookieJar();
// Capture a set cookie
const firstResponse = await cycleTLS.get(
"https://httpbin.org/cookies/set?freeform=test",
{
disableRedirect: true,
}
);
// Now use the processCookies function to add the cookies from the response headers to the cookie jar
await processCookies(
firstResponse,
"https://httpbin.org/cookies/set?freeform=test",
cookieJar
);
// Now send a second to verify we have our cookies
const secondResponse = await cycleTLS.get("https://httpbin.org/cookies", {
headers: {
cookie: await cookieJar.getCookieString("https://httpbin.org/cookies"),
},
});
// Verify cookies were set
const data = await secondResponse.json();
console.log(data)
/* Expected
{
"cookies": {
"freeform": "test"
}
}
*/
await cycleTLS.exit();
})();
async function processCookies(response, url, cookieJar) {
if (response.headers["Set-Cookie"] instanceof Array) {
response.headers["Set-Cookie"].map(
async (cookieString) => await cookieJar.setCookie(cookieString, url)
);
} else {
await cookieJar.setCookie(response.headers["Set-Cookie"], url);
}
}
### CookieJar in Golang
go
package main
import (
"github.com/Danny-Dasilva/CycleTLS/cycletls"
"log"
"net/http/cookiejar"
"net/url"
"strings"
)
func main() {
client := cycletls.Init()
jar, err := cookiejar.New(nil)
if err != nil {
log.Fatal(err)
}
// First request to set cookie
firstResponse, err := client.Do("https://httpbin.org/cookies/set?a=1&b=2&c=3", cycletls.Options{
Body: "",
Ja3: "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36",
DisableRedirect: true,
},
"GET")
if err != nil {
log.Fatal(err)
}
firstURL, _ := url.Parse(firstResponse.FinalUrl)
jar.SetCookies( firstURL, firstResponse.Cookies)
// Second request to verify cookies, including the cookies from the first response
secondResponse, err := client.Do("https://httpbin.org/cookies", cycletls.Options{
Body: "",
Ja3: "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36",
Headers: map[string]string{
"Cookie": getHeadersFromJar(jar, firstURL),
},
}, "GET")
if err != nil {
log.Fatal(err)
}
log.Println("Second Response body:", secondResponse.Body)
}
func getHeadersFromJar(jar *cookiejar.Jar, url *url.URL) string {
cookies := jar.Cookies(url)
var cookieStrs []string
for _, cookie := range cookies {
cookieStrs = append(cookieStrs, cookie.Name+"="+cookie.Value)
}
return strings.Join(cookieStrs, "; ")
}
How do I send multipart/form-data in CycleTLS
js
const initCycleTLS = require("cycletls");
const FormData = require('form-data');
(async () => {
const cycleTLS = await initCycleTLS();
const formData = new FormData();
formData.append("key1", "value1");
formData.append("key2", "value2");
const response = await cycleTLS('http://httpbin.org/post', {
body: formData,
headers: formData.getHeaders(), // Use formData.getHeaders() for proper content-type
}, 'post');
const data = await response.json();
console.log(data);
await cycleTLS.exit();
})();
### Javascript File form-data
js
const initCycleTLS = require("cycletls");
const FormData = require('form-data');
const fs = require('fs');
(async () => {
const cycleTLS = await initCycleTLS();
const formData = new FormData();
const fileStream = fs.createReadStream("../go.mod");
formData.append('file', fileStream);
const response = await cycleTLS('http://httpbin.org/post', {
body: formData,
headers: formData.getHeaders(), // Use formData.getHeaders() for proper content-type
}, 'post');
const data = await response.json();
console.log(data);
await cycleTLS.exit();
})();
### Golang Text form-data
golang
package main
import (
"bytes"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
"log"
"mime/multipart"
)
func main() {
client := cycletls.Init()
// Prepare a buffer to write our multipart form
var requestBody bytes.Buffer
multipartWriter := multipart.NewWriter(&requestBody)
// Add form fields
multipartWriter.WriteField("key1", "value1")
multipartWriter.WriteField("key2", "value2")
contentType := multipartWriter.FormDataContentType()
// Close the writer before making the request
multipartWriter.Close()
response, err := client.Do("http://httpbin.org/post", cycletls.Options{
Body: requestBody.String(),
Headers: map[string]string{
"Content-Type": contentType,
},
}, "POST")
if err != nil {
log.Print("Request Failed: " + err.Error())
}
log.Println(response.Body)
}
### Golang file upload form-data
golang
package main
import (
"github.com/Danny-Dasilva/CycleTLS/cycletls"
"bytes"
"io"
"log"
"mime/multipart"
"os"
)
func main() {
client := cycletls.Init()
// Prepare a buffer to write our multipart form
var requestBody bytes.Buffer
multipartWriter := multipart.NewWriter(&requestBody)
// Add a file
fileWriter, err := multipartWriter.CreateFormFile("fieldname", "filename")
if err != nil {
log.Fatal("CreateFormFile Error: ", err)
}
// Open the file that you want to upload
file, err := os.Open("path/to/your/file")
if err != nil {
log.Fatal("File Open Error: ", err)
}
defer file.Close()
// Copy the file to the multipart writer
_, err = io.Copy(fileWriter, file)
if err != nil {
log.Fatal("File Copy Error: ", err)
}
// Close the writer before making the request
contentType := multipartWriter.FormDataContentType()
multipartWriter.Close()
response, err := client.Do("http://httpbin.org/post", cycletls.Options{
Body: requestBody.String(),
Headers: map[string]string{
"Content-Type": contentType,
},
}, "POST")
if err != nil {
log.Print("Request Failed: " + err.Error())
}
log.Println(response.Body)
}
If requested encoding helpers can be added to the repo for golang
How do I send a application/x-www-form-urlencoded Post request
js
const initCycleTLS = require("cycletls");
(async () => {
const cycleTLS = await initCycleTLS();
const urlEncodedData = new URLSearchParams();
urlEncodedData.append('key1', 'value1');
urlEncodedData.append('key2', 'value2');
const response = await cycleTLS('http://httpbin.org/post', {
body: urlEncodedData,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}, 'post');
const data = await response.json();
console.log(data);
await cycleTLS.exit();
})();
### Golang application/x-www-form-urlencoded form
golang
package main
import (
"log"
"net/url"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
client := cycletls.Init()
// Prepare form data
form := url.Values{}
form.Add("key1", "value1")
form.Add("key2", "value2")
response, err := client.Do("http://httpbin.org/post", cycletls.Options{
Body: form.Encode(),
Headers: map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
},
}, "POST")
if err != nil {
log.Print("Request Failed: " + err.Error())
}
log.Println(response.Body)
}
How do I download images and videos?
Content-Type
headers are returned as raw binary data stored in a string format.
Supported Media Types
image/svg+xml
image/webp
image/jpeg
image/png
image/gif
application/pdf
video/mp4
video/webm
video/avi
video/quicktime
Important: The media data is NOT base64 encoded. It is raw binary data converted to a string format.
To write them to a file you can use the below methods
### Javascript Media Download Example
js
const initCycleTLS = require("cycletls");
var fs = require("fs");
(async () => {
const cycleTLS = await initCycleTLS();
// Download image using arrayBuffer() - correct method
const jpegImage = await cycleTLS("http://httpbin.org/image/jpeg", {
ja3: "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0",
userAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
});
const jpegBuffer = await jpegImage.arrayBuffer();
fs.writeFileSync('./images/output.jpeg', Buffer.from(jpegBuffer));
console.log('JPEG image downloaded');
// Download PNG
const pngImage = await cycleTLS("http://httpbin.org/image/png", {
ja3: "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0",
userAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
});
const pngBuffer = await pngImage.arrayBuffer();
fs.writeFileSync('./images/output.png', Buffer.from(pngBuffer));
console.log('PNG image downloaded');
// Download WebP
const webpImage = await cycleTLS("http://httpbin.org/image/webp", {
ja3: "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0",
userAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
});
const webpBuffer = await webpImage.arrayBuffer();
fs.writeFileSync('./images/output.webp', Buffer.from(webpBuffer));
console.log('WebP image downloaded');
// Download video
const videoResponse = await cycleTLS("https://sample-videos.com/zip/10/mp4/SampleVideo_360x240_1mb.mp4", {
ja3: "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0",
userAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
});
const videoBuffer = await videoResponse.arrayBuffer();
fs.writeFileSync('./videos/sample_video.mp4', Buffer.from(videoBuffer));
console.log('Video downloaded');
await cycleTLS.exit();
})();
### Streaming Binary Data Example
For large files or real-time binary streaming:
js
const initCycleTLS = require("cycletls");
var fs = require("fs");
(async () => {
const cycleTLS = await initCycleTLS();
// Stream large video file
const response = await cycleTLS("https://sample-videos.com/zip/25/mp4/SampleVideo_1280x720_5mb.mp4", {
responseType: 'stream',
ja3: "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0",
userAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
});
const stream = response.data;
const writeStream = fs.createWriteStream('./videos/large_video.mp4');
let totalSize = 0;
stream.on('data', (chunk) => {
totalSize += chunk.length;
console.log(`Downloaded ${totalSize} bytes`);
writeStream.write(chunk);
});
stream.on('end', () => {
writeStream.end();
console.log(`Stream complete. Total size: ${totalSize} bytes`);
await cycleTLS.exit();
});
stream.on('error', (error) => {
console.error('Stream error:', error);
writeStream.end();
await cycleTLS.exit();
});
})();
### Golang Media Download Example
golang
package main
import (
"log"
"os"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func writeMedia(filepath string, data string) error {
// Convert string body to bytes (raw binary data)
bodyBytes := []byte(data)
f, err := os.Create(filepath)
if err != nil {
return err
}
defer f.Close()
if _, err := f.Write(bodyBytes); err != nil {
return err
}
return f.Sync()
}
func main() {
client := cycletls.Init()
defer client.Close()
// Download image
response, err := client.Do("http://httpbin.org/image/jpeg", cycletls.Options{
Body: "",
Ja3: "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-21,29-23-24,0",
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36",
}, "GET")
if err != nil {
log.Fatal("Image download failed: ", err)
}
if err := writeMedia("test.jpeg", response.Body); err != nil {
log.Fatal("Image write failed: ", err)
}
log.Println("Image downloaded successfully")
// Download video
videoResponse, err := client.Do("https://sample-videos.com/zip/10/mp4/SampleVideo_360x240_1mb.mp4", cycletls.Options{
Body: "",
Ja3: "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-21,29-23-24,0",
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36",
}, "GET")
if err != nil {
log.Fatal("Video download failed: ", err)
}
if err := writeMedia("sample_video.mp4", videoResponse.Body); err != nil {
log.Fatal("Video write failed: ", err)
}
log.Println("Video downloaded successfully")
}
Additional file type support is planned.
Feel free to open an Issue with a feature request for specific file type support.
How do I use Connection Reuse?
go
package main
import (
"log"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
// Initialize without worker pool for better connection management
client := cycletls.Init(false)
defer client.Close()
// Enable connection reuse in the options
options := cycletls.Options{
Ja3: "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36",
EnableConnectionReuse: true, // Enable connection reuse
}
// First request - establishes connection
resp1, err := client.Do("https://httpbin.org/get", options, "GET")
if err != nil {
log.Fatal("First request failed: ", err)
}
log.Println("First request status:", resp1.Status)
// Second request - reuses connection
resp2, err := client.Do("https://httpbin.org/headers", options, "GET")
if err != nil {
log.Fatal("Second request failed: ", err)
}
log.Println("Second request status:", resp2.Status)
// Connection is reused for requests to the same host
}
How do I use HTTP/3 and QUIC?
go
package main
import (
"log"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
client := cycletls.Init()
defer client.Close()
// Force HTTP/3
response, err := client.Do("https://cloudflare-quic.com/", cycletls.Options{
ForceHTTP3: true,
UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
InsecureSkipVerify: true,
}, "GET")
if err != nil {
log.Fatal("Request failed: ", err)
}
log.Println("Response over HTTP/3:", response.Status)
}
### Golang QUIC Fingerprinting
go
package main
import (
"log"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
client := cycletls.Init()
defer client.Close()
// Custom QUIC fingerprint
quicFingerprint := "16030106f2010006ee03039a2b98d81139db0e128ea09eff6874549c219b543fb6dbaa7e4dbfe9e31602c620ce04c4026f019442affade7fed8ba66e022e186f77f1c670fd992f33c0143f120020aaaa130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035010006851a1a00000010000e000c02683208687474702f312e31002b000706dada03040303..."
response, err := client.Do("https://cloudflare-quic.com/", cycletls.Options{
QUICFingerprint: quicFingerprint,
ForceHTTP3: true,
UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
InsecureSkipVerify: true,
}, "GET")
if err != nil {
log.Fatal("Request failed: ", err)
}
log.Println("Response with QUIC fingerprint:", response.Status)
}
### Golang HTTP/3 Transport Direct Usage
go
package main
import (
"crypto/tls"
"log"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
http "github.com/Danny-Dasilva/fhttp"
)
func main() {
// Create TLS config
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
// Create HTTP/3 transport
transport := cycletls.NewHTTP3Transport(tlsConfig)
// Create request
req, err := http.NewRequest("GET", "https://cloudflare-quic.com/", nil)
if err != nil {
log.Fatal("Failed to create request: ", err)
}
// Send request
resp, err := transport.RoundTrip(req)
if err != nil {
log.Fatal("Request failed: ", err)
}
defer resp.Body.Close()
log.Println("Direct HTTP/3 response:", resp.Status)
}
How do I use WebSocket support?
js
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
// WebSocket connection with TLS fingerprinting
const wsResponse = await cycleTLS.ws('wss://echo.websocket.org', {
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0',
headers: {
'Sec-WebSocket-Protocol': 'echo-protocol'
}
});
// Check connection status
if (wsResponse.status === 101) {
console.log('WebSocket upgrade successful');
console.log('Response headers:', wsResponse.headers);
}
await cycleTLS.exit();
})();
### Golang WebSocket Example
go
package main
import (
"log"
"net/http"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
"github.com/gorilla/websocket"
utls "github.com/refraction-networking/utls"
)
func main() {
// Create TLS config
tlsConfig := &utls.Config{
InsecureSkipVerify: true,
}
// Create headers
headers := make(http.Header)
headers.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36")
// Create WebSocket client
wsClient := cycletls.NewWebSocketClient(tlsConfig, headers)
// Connect to WebSocket server
conn, resp, err := wsClient.Connect("wss://echo.websocket.org/")
if err != nil {
log.Fatal("WebSocket connection failed: ", err)
}
defer conn.Close()
log.Println("WebSocket connected, status:", resp.StatusCode)
// Send message
testMessage := "Hello, WebSocket!"
if err := conn.WriteMessage(websocket.TextMessage, []byte(testMessage)); err != nil {
log.Fatal("Failed to send message: ", err)
}
// Read response
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Fatal("Failed to read message: ", err)
}
log.Printf("Received message type %d: %s\n", messageType, string(message))
}
### Golang WebSocket Response Wrapper
go
package main
import (
"log"
"net/http"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
"github.com/gorilla/websocket"
utls "github.com/refraction-networking/utls"
)
func main() {
// Setup WebSocket client
tlsConfig := &utls.Config{
InsecureSkipVerify: true,
}
headers := make(http.Header)
headers.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
wsClient := cycletls.NewWebSocketClient(tlsConfig, headers)
// Connect
conn, _, err := wsClient.Connect("wss://echo.websocket.org/")
if err != nil {
log.Fatal("Connection failed: ", err)
}
// Create response wrapper
wsResponse := &cycletls.WebSocketResponse{
Conn: conn,
}
defer wsResponse.Close()
// Send message using wrapper
if err := wsResponse.Send(websocket.TextMessage, []byte("Hello!")); err != nil {
log.Fatal("Send failed: ", err)
}
// Receive message using wrapper
messageType, message, err := wsResponse.Receive()
if err != nil {
log.Fatal("Receive failed: ", err)
}
log.Printf("Received: %s (type: %d)
", string(message), messageType)
}
How do I use Server-Sent Events (SSE)?
js
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
// SSE connection with TLS fingerprinting
const sseResponse = await cycleTLS.sse('https://example.com/events', {
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0',
headers: {
'Accept': 'text/event-stream',
'Cache-Control': 'no-cache'
}
});
// Parse real-time events
const eventData = await sseResponse.text();
console.log('SSE events:', eventData);
await cycleTLS.exit();
})();
### JavaScript SSE with Streaming
js
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
// SSE with streaming for real-time processing
const response = await cycleTLS.get('https://example.com/events', {
responseType: 'stream',
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0',
headers: {
'Accept': 'text/event-stream',
'Cache-Control': 'no-cache'
}
});
// Process SSE stream in real-time
const stream = response.data;
let buffer = '';
stream.on('data', (chunk) => {
buffer += chunk.toString();
const lines = buffer.split('
');
// Process complete lines, keep incomplete line in buffer
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data:')) {
const eventData = line.substring(5).trim();
console.log('Event data:', eventData);
} else if (line.startsWith('event:')) {
const eventType = line.substring(6).trim();
console.log('Event type:', eventType);
}
}
});
stream.on('end', () => {
console.log('SSE stream ended');
await cycleTLS.exit();
});
stream.on('error', (error) => {
console.error('SSE stream error:', error);
await cycleTLS.exit();
});
})();
### Golang SSE Client Example
go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
fhttp "github.com/Danny-Dasilva/fhttp"
)
func main() {
// Create HTTP client
httpClient := &fhttp.Client{
Timeout: 30 * time.Second,
}
// Create headers
headers := make(fhttp.Header)
headers.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36")
headers.Set("Accept", "text/event-stream")
// Create SSE client
sseClient := cycletls.NewSSEClient(httpClient, headers)
// Connect to SSE server with timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
sseResp, err := sseClient.Connect(ctx, "http://localhost:3333/events")
if err != nil {
log.Fatal("SSE connection failed: ", err)
}
defer sseResp.Close()
// Read events
eventCount := 0
for eventCount < 5 {
event, err := sseResp.NextEvent()
if err != nil {
log.Printf("Error reading event: %v\n", err)
break
}
if event != nil {
eventCount++
fmt.Printf("Event #%d:\n", eventCount)
fmt.Printf(" Type: %s\n", event.Event)
fmt.Printf(" Data: %s\n", event.Data)
fmt.Printf(" ID: %s\n", event.ID)
}
}
}
### Golang SSE with Browser Configuration
go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
// Create browser configuration with TLS fingerprinting
browser := cycletls.Browser{
UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
JA3: "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
InsecureSkipVerify: true,
}
// Connect to SSE endpoint with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
response, err := browser.SSEConnect(ctx, "http://127.0.0.1:3333/events")
if err != nil {
log.Fatal("SSE connection failed: ", err)
}
defer response.Close()
// Process events with detailed parsing
for {
event, err := response.NextEvent()
if err != nil {
log.Printf("Event stream ended: %v\n", err)
break
}
if event != nil && event.Data != "" {
fmt.Printf("Event Type: %s\n", event.Event)
fmt.Printf("Event ID: %s\n", event.ID)
fmt.Printf("Event Data: %s\n", event.Data)
// Break after receiving specific event
if event.Data == "done" {
break
}
}
}
}
### Browser.SSEConnect Method
The Browser.SSEConnect
method provides SSE connections with TLS fingerprinting support:
go
type Browser struct {
UserAgent string
JA3 string
JA4r string
HTTP2Fingerprint string
QUICFingerprint string
InsecureSkipVerify bool
ForceHTTP1 bool
ForceHTTP3 bool
}
// SSEConnect establishes an SSE connection with browser fingerprinting
func (b *Browser) SSEConnect(ctx context.Context, url string) (*SSEResponse, error)
### SSE Event Structure
go
type SSEEvent struct {
ID string // Event ID from server
Event string // Event type (custom event names)
Data string // Event data payload
Retry int64 // Reconnection time in milliseconds
}
How do I use JA4R fingerprinting?
ja4r
(raw format) to configure fingerprints. JA4 hashes are for observation only.
JA4R is the raw format for configuring TLS fingerprints with explicit cipher suites and extensions.
### Golang JA4R Fingerprinting
go
package main
import (
"log"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
client := cycletls.Init()
defer client.Close()
// Use both JA3 and JA4R fingerprints
response, err := client.Do("https://tls.peet.ws/api/clean", cycletls.Options{
Ja3: "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
Ja4r: "t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0000,0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601", // JA4R fingerprint (raw format)
UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36",
}, "GET")
if err != nil {
log.Fatal("Request failed: ", err)
}
log.Println("Response with JA4R:", response.Status)
}
How do I set a custom SNI (domain fronting)?
Host
header. This enables domain fronting scenarios where the handshake SNI differs from the request host.
JavaScript/TypeScript:
js
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
const resp = await cycleTLS('https://127.0.0.1:8443', {
serverName: 'front.example', // TLS SNI used in handshake
headers: { Host: 'real.example' }, // HTTP Host header inside the request
insecureSkipVerify: true, // for local/self-signed testing
ja3: '771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0',
userAgent: 'Mozilla/5.0 ... Chrome/101.0.4951.54 Safari/537.36'
}, 'GET');
console.log(await resp.text());
await cycleTLS.exit();
})();
Golang:
go
client := cycletls.Init()
response, err := client.Do("https://127.0.0.1:8443", cycletls.Options{
ServerName: "front.example", // TLS SNI
Headers: map[string]string{"Host": "real.example"}, // HTTP Host
InsecureSkipVerify: true,
Ja3: "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
UserAgent: "Mozilla/5.0 ... Chrome/101.0.4951.54 Safari/537.36",
}, "GET")
Notes:
- When serverName
is provided, it is used for the TLS handshake; the library will not overwrite your Host
header.
- JA4R fingerprints that include SNI (extension 0x0000) will be constructed using the provided serverName
.
- Protocol support: serverName
works with HTTP/1.1, HTTP/2, HTTP/3, WebSocket (wss://
), and SSE (https://
).
WebSocket (wss) with custom SNI:
ts
import initCycleTLS from 'cycletls';
(async () => {
const cycleTLS = await initCycleTLS();
const ws = await cycleTLS.ws('wss://127.0.0.1:8443/socket', {
serverName: 'front.example',
headers: { Host: 'real.example' },
insecureSkipVerify: true,
});
ws.onMessage(msg => console.log('message:', msg));
await ws.close();
await cycleTLS.exit();
})();
SSE with custom SNI:
ts
import initCycleTLS from 'cycletls';
(async () => {
const cycleTLS = await initCycleTLS();
const sse = await cycleTLS.sse('https://127.0.0.1:8443/events', {
serverName: 'front.example',
headers: { Host: 'real.example' },
insecureSkipVerify: true,
});
sse.onEvent(ev => console.log('event:', ev));
await sse.close();
await cycleTLS.exit();
})();
How do I set/force HTTP1 or HTTP3
In golang set ForceHTTP1
in Options
package main
import (
"github.com/Danny-Dasilva/CycleTLS/cycletls"
"log"
)
func main() {
client := cycletls.Init()
response, err := client.Do("https://tls.peet.ws/api/all", cycletls.Options{
ForceHTTP1: true,
}, "GET")
if err != nil {
log.Print("Request Failed: " + err.Error())
}
log.Println(response.Body,) //You can verify the HTTP_Version in the response
}
In JS/TS set forceHTTP1
in Options
const initCycleTLS = require('cycletls');
// Typescript: import initCycleTLS from 'cycletls';
(async () => {
const cycleTLS = await initCycleTLS();
const response = await cycleTLS('https://ja3er.com/json', {
body: '',
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
userAgent:
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0',
forceHTTP1: true, // Set this field to force HTTP/1.1
});
const data = await response.json();
console.log(data);
// You can verify the HTTP_Version in the response
await cycleTLS.exit();
})();
Forcing HTTP/3
Similarly, you can force HTTP/3 protocol usage:
In JS/TS set forceHTTP3
in Options
```js const initCycleTLS = require('cycletls'); // Typescript: import initCycleTLS from 'cycletls';
(async () => { const cycleTLS = await initCycleTLS();
const response = await cycleTLS('https://www.google.com/', { body: '', ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0', userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.