Skip to content

Danny-Dasilva/CycleTLS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CycleTLS

CycleTLS

Accepting Community Support and PR's

build GoDoc license Go Report Card npm version chat on Discord

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
  cycleTLS.exit();

})();

JA4 TLS Fingerprinting

JA4 is an enhanced TLS fingerprinting method that provides more detailed client identification:

JavaScript Example

const initCycleTLS = require('cycletls');

(async () => {
  const cycleTLS = await initCycleTLS();

  // Firefox JA4 fingerprint
  const response = await cycleTLS('https://tls.peet.ws/api/all', {
	ja4: 't13d1717h2_5b57614c22b0_f2748d6cd58d'
  });

  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);
  
  cycleTLS.exit();
})();

Golang JA4 Example

package main

import (
	"log"
	"github.com/Danny-Dasilva/CycleTLS/cycletls"
)

func main() {
	client := cycletls.Init()
	defer client.Close()

	// Chrome JA4 fingerprint
	response, err := client.Do("https://tls.peet.ws/api/all", cycletls.Options{
		Ja4: "t13d1517h2_8daaf6152771_7e51fdad25f2",
		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 JA4:", 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);
  
  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 Firefox browser fingerprint
  const response = await cycleTLS('https://tls.peet.ws/api/all', {
	ja4: 't13d1717h2_5b57614c22b0_f2748d6cd58d',
	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);
  
  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");
	cycleTLS.exit();
  });

  stream.on('error', (error) => {
	console.error('Stream error:', error);
	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');
	  cycleTLS.exit();
	});

	response.data.on('error', (error) => {
	  console.error('Stream error:', error);
	  cycleTLS.exit();
	});

  } catch (error) {
	console.error('Request failed:', error);
	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

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/")
	...
}

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',
  // JA4 token for enhanced fingerprinting
  ja4: 't13d1516h2_8daaf6152771_02713d6af862',
  // 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 decompressed
  • deflate - Automatically decompressed
  • brotli - 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);

  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 {
	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());
  
  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
  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

There are two simple ways to interface with cookies

Javascript Simple Cookie Configuration

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"
	}
  }
  */
  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

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

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"
	}
  }
  */
  cycleTLS.exit();
})();

Golang Configure Cookies

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?

CookieJar in 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"
	}
  }
  */
  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

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

Javascript Text form-data

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);

  cycleTLS.exit();
})();

Javascript File form-data

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);

  cycleTLS.exit();
})();

Golang Text form-data

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

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

Javascript application/x-www-form-urlencoded form

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);

  cycleTLS.exit();
})();

Golang application/x-www-form-urlencoded form

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?

Images and videos with supported 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

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');

  cycleTLS.exit();
})();

Streaming Binary Data Example

For large files or real-time binary streaming:

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`);
	cycleTLS.exit();
  });

  stream.on('error', (error) => {
	console.error('Stream error:', error);
	writeStream.end();
	cycleTLS.exit();
  });
})();

Golang Media Download Example

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?

Connection reuse allows you to reuse TLS connections across multiple requests to the same host, reducing handshake overhead and improving performance.

Golang Connection Reuse

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?

CycleTLS now supports HTTP/3 over QUIC protocol with custom QUIC fingerprinting.

Golang HTTP/3 Basic Usage

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

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

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?

CycleTLS provides a WebSocket client that supports custom TLS fingerprinting.

JavaScript WebSocket Example

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);
  }

  cycleTLS.exit();
})();

Golang WebSocket Example

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

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)?

CycleTLS supports Server-Sent Events for real-time data streaming from servers.

JavaScript SSE Example

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);

  cycleTLS.exit();
})();

JavaScript SSE with Streaming

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');
	cycleTLS.exit();
  });

  stream.on('error', (error) => {
	console.error('SSE stream error:', error);
	cycleTLS.exit();
  });
})();

Golang SSE Client Example

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

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:

type Browser struct {
	UserAgent          string
	JA3                string
	JA4                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

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 JA4 fingerprinting?

JA4 is an enhanced TLS fingerprinting method that provides additional client identification capabilities.

Golang JA4 Fingerprinting

package main

import (
	"log"
	"github.com/Danny-Dasilva/CycleTLS/cycletls"
)

func main() {
	client := cycletls.Init()
	defer client.Close()

	// Use both JA3 and JA4 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",
		Ja4:       "t13d1516h2_8daaf6152771_02713d6af862", // JA4 fingerprint
		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 JA4:", response.Status)
}

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
  cycleTLS.exit();

})();

Forcing HTTP/3

Similarly, you can force HTTP/3 protocol usage:

In JS/TS set forceHTTP3 in Options

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.0) Gecko/20100101 Firefox/87.0',
    forceHTTP3: true, // Set this field to force HTTP/3 via QUIC
  });

  const data = await response.json();
  console.log(data);
  // HTTP/3 requests use QUIC protocol
  cycleTLS.exit();

})();

Cross Compiling for other platforms

Natively the 3 Operating System types linux, darwin , windows should cover most use cases.

You can use the built in Golang cross compiling commands go build to compile for another operating system.

As an example for linux arm you need to pass in the GOOS and GOARCH arguments

$ GOOS=linux GOARCH=arm go build -o ./dist/index ./golang && chmod +x ./dist/index

With the above command you can simply run ./index and CycleTLS should function as intended.

Use this gist for different Operating Systems that support cross-compilation and feel free to open an Issue with a feature request for your specific operating system use case.

LICENSE

GPL3 LICENSE SYNOPSIS

TL;DR* Here's what the GPL3 license entails:

1. Anyone can copy, modify and distribute this software.
2. You have to include the license and copyright notice with each and every distribution.
3. You can use this software privately.
4. You can use this software for commercial purposes.
5. Source code MUST be made available when the software is distributed.
6. Any modifications of this code base MUST be distributed with the same license, GPLv3.
7. This software is provided without warranty.
8. The software author or license can not be held liable for any damages inflicted by the software.

More information on about the LICENSE can be found here