The TLS Client supports HTTP and SOCKS5 proxies with authentication, allowing you to route traffic through proxy servers. You can configure proxies at initialization or change them dynamically at runtime.
Basic proxy configuration
Set a proxy when creating the client:
import (
tls_client "github.com/bogdanfinn/tls-client"
"github.com/bogdanfinn/tls-client/profiles"
)
options := []tls_client.HttpClientOption{
tls_client.WithTimeoutSeconds(30),
tls_client.WithClientProfile(profiles.Chrome_133),
tls_client.WithProxyUrl("http://user:[email protected]:8080"),
}
client, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
if err != nil {
log.Fatal(err)
}
// All requests will use the proxy
resp, err := client.Get("https://api.ipify.org?format=json")
The client supports multiple proxy protocols:
HTTP proxy
// Without authentication
tls_client.WithProxyUrl("http://proxy.example.com:8080")
// With authentication
tls_client.WithProxyUrl("http://username:[email protected]:8080")
SOCKS5 proxy
// Without authentication
tls_client.WithProxyUrl("socks5://proxy.example.com:1080")
// With authentication
tls_client.WithProxyUrl("socks5://username:[email protected]:1080")
// SOCKS5h (DNS resolution through proxy)
tls_client.WithProxyUrl("socks5h://username:[email protected]:1080")
Use socks5h:// instead of socks5:// if you want DNS queries to be resolved by the proxy server rather than locally.
Charles Proxy for debugging
Quick setup for local Charles Proxy debugging:
// Uses default 127.0.0.1:8888
tls_client.WithCharlesProxy("127.0.0.1", "8888")
// Or with default values
tls_client.WithCharlesProxy("", "")
This is equivalent to:
tls_client.WithProxyUrl("http://127.0.0.1:8888")
Dynamic proxy rotation
Change the proxy at runtime without recreating the client:
client, err := tls_client.NewHttpClient(
tls_client.NewNoopLogger(),
tls_client.WithTimeoutSeconds(30),
tls_client.WithClientProfile(profiles.Chrome_133),
tls_client.WithProxyUrl("http://user:[email protected]:8080"),
)
// Make request with first proxy
resp1, err := client.Get("https://api.ipify.org?format=json")
if err != nil {
log.Fatal(err)
}
defer resp1.Body.Close()
// Switch to second proxy
err = client.SetProxy("http://user:[email protected]:8080")
if err != nil {
log.Fatal(err)
}
// Make request with second proxy
resp2, err := client.Get("https://api.ipify.org?format=json")
if err != nil {
log.Fatal(err)
}
defer resp2.Body.Close()
Removing a proxy
Set an empty string to remove the proxy:
err = client.SetProxy("")
if err != nil {
log.Fatal(err)
}
// Subsequent requests will not use a proxy
Getting current proxy
Retrieve the currently configured proxy:
currentProxy := client.GetProxy()
log.Printf("Current proxy: %s", currentProxy)
Complete example with proxy rotation
package main
import (
"encoding/json"
"fmt"
"io"
"log"
tls_client "github.com/bogdanfinn/tls-client"
"github.com/bogdanfinn/tls-client/profiles"
)
type IPResponse struct {
IP string `json:"ip"`
}
func main() {
// List of proxies to rotate through
proxies := []string{
"http://user1:[email protected]:8080",
"http://user2:[email protected]:8080",
"http://user3:[email protected]:8080",
}
options := []tls_client.HttpClientOption{
tls_client.WithTimeoutSeconds(30),
tls_client.WithClientProfile(profiles.Chrome_133),
tls_client.WithProxyUrl(proxies[0]),
}
client, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
if err != nil {
log.Fatal(err)
}
// Make requests with different proxies
for i, proxy := range proxies {
// Set the proxy
if i > 0 { // First proxy is already set
err := client.SetProxy(proxy)
if err != nil {
log.Printf("Failed to set proxy %s: %v", proxy, err)
continue
}
}
// Make request
resp, err := client.Get("https://api.ipify.org?format=json")
if err != nil {
log.Printf("Request failed with proxy %d: %v", i+1, err)
continue
}
body, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
log.Printf("Failed to read response: %v", err)
continue
}
var ipResp IPResponse
if err := json.Unmarshal(body, &ipResp); err != nil {
log.Printf("Failed to parse response: %v", err)
continue
}
fmt.Printf("Proxy %d - IP: %s\n", i+1, ipResp.IP)
}
}
Proxy authentication
Authentication credentials are included in the proxy URL:
// URL encode special characters in credentials
username := "user@domain"
password := "p@ssw0rd!"
// Proper encoding
proxyURL := fmt.Sprintf("http://%s:%s@proxy.example.com:8080",
url.QueryEscape(username),
url.QueryEscape(password),
)
tls_client.WithProxyUrl(proxyURL)
Always URL-encode proxy credentials if they contain special characters like @, :, or /.
For HTTP CONNECT proxies, you can specify custom headers:
import http "github.com/bogdanfinn/fhttp"
connectHeaders := http.Header{
"Proxy-Authorization": {"Bearer token123"},
"X-Custom-Header": {"custom-value"},
}
options := []tls_client.HttpClientOption{
tls_client.WithProxyUrl("http://proxy.example.com:8080"),
tls_client.WithConnectHeaders(connectHeaders),
}
client, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
Advanced: Custom proxy dialer
For advanced use cases, implement a custom proxy dialer:
import (
"context"
"net"
"time"
http "github.com/bogdanfinn/fhttp"
"golang.org/x/net/proxy"
)
customDialerFactory := func(
proxyURL string,
timeout time.Duration,
localAddr *net.TCPAddr,
connectHeaders http.Header,
logger tls_client.Logger,
) (proxy.ContextDialer, error) {
// Implement custom proxy logic
// This is called when the client needs to create a new connection
return &customDialer{
proxyURL: proxyURL,
timeout: timeout,
}, nil
}
tls_client.WithProxyDialerFactory(customDialerFactory)
When using WithProxyDialerFactory(), you cannot also use WithProxyUrl(). Only one proxy configuration method can be active at a time.
Proxy error handling
Always handle proxy errors gracefully:
err := client.SetProxy("http://invalid-proxy:9999")
if err != nil {
log.Printf("Failed to set proxy: %v", err)
// Roll back to previous proxy
err = client.SetProxy("http://working-proxy.example.com:8080")
if err != nil {
log.Fatal("Failed to roll back to working proxy")
}
}
The client automatically rolls back to the previous proxy if setting a new proxy fails.
Testing proxy configuration
Verify your proxy is working correctly:
import http "github.com/bogdanfinn/fhttp"
options := []tls_client.HttpClientOption{
tls_client.WithTimeoutSeconds(10),
tls_client.WithClientProfile(profiles.Chrome_133),
tls_client.WithProxyUrl("http://user:[email protected]:8080"),
}
client, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
if err != nil {
log.Fatal(err)
}
// Test proxy connectivity
req, err := http.NewRequest("GET", "https://api.ipify.org?format=json", nil)
if err != nil {
log.Fatal(err)
}
resp, err := client.Do(req)
if err != nil {
log.Printf("Proxy test failed: %v", err)
return
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
body, _ := io.ReadAll(resp.Body)
log.Printf("Proxy working. External IP: %s", string(body))
} else {
log.Printf("Unexpected status code: %d", resp.StatusCode)
}
Best practices
Reuse the same client instance to benefit from connection pooling with the proxy.
Implement fallback logic to switch proxies when one fails:
proxies := []string{"http://proxy1", "http://proxy2", "http://proxy3"}
for _, proxy := range proxies {
client.SetProxy(proxy)
resp, err := client.Get(url)
if err == nil {
// Success
return resp, nil
}
}
return nil, errors.New("all proxies failed")
Track which proxies are performing well:
start := time.Now()
resp, err := client.Get(url)
duration := time.Since(start)
log.Printf("Proxy %s responded in %v", client.GetProxy(), duration)
Validate proxy before use
Test proxy connectivity before adding it to your rotation pool.
Proxy configuration is automatically applied to all connection types, including HTTP/1.1, HTTP/2, and HTTP/3.
Next steps