Help us shape the future of Go! Take the 2026 Go Developer Survey

unblock

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 24, 2026 License: MIT Imports: 12 Imported by: 0

README

unblock

HTTP client Go yang menyerupai browser di level jaringan, dan tahu kapan dirinya diblokir.

c := unblock.New(unblock.Options{})
resp, err := c.Get(ctx, "https://contoh.id")
if b := resp.Blocked(); b != nil {
    fmt.Println(b.Kind, b.Hint) // CF_CHALLENGE / CF_BLOCK_1020 / WAF_GENERIC / ...
}

Tiga hal yang dikerjakan, dan satu yang tidak.

1. Fingerprint TLS/JA3 + HTTP/2

net/http, requests, dan httpx punya sidik jari yang khas dan gampang dikenali. Saat konek HTTPS, client mengirim TLS ClientHello berisi cipher suite, urutan extension, kurva, dan ALPN — WAF menghitung itu jadi hash (JA3/JA4). Ditambah sidik jari HTTP/2: nilai SETTINGS, WINDOW_UPDATE, urutan pseudo-header.

unblock memakai bogdanfinn/tls-client (ClientHello via uTLS, h2 via fhttp) sehingga sidik jarinya menyerupai Chrome/Safari/Firefox asli:

c := unblock.New(unblock.Options{
    Profile: "chrome_133", // kosong = profil Chrome default (versi terbaru)
})

Setara konsepnya dengan curl_cffi di Python, tapi tanpa proses terpisah.

2. Fallback HTTP/1.1 otomatis

Sebagian server menolak negosiasi HTTP/2 dengan RST_STREAM (stream ID 1; INTERNAL_ERROR) walau TLS-nya lolos. unblock mendeteksi pola error itu, lalu mengulang sekali lewat HTTP/1.1 dengan fingerprint TLS yang sama.

Percobaan pertama diberi separuh budget waktu, supaya retry tidak kehabisan waktu karena percobaan pertama menggantung. Response.Attempts dan Response.HTTP1 melaporkan jalur mana yang dipakai.

resp, _ := c.Get(ctx, url)
fmt.Println(resp.Attempts) // 1, atau 2 kalau fallback dipakai

Paksa selalu HTTP/1.1 dengan Options.ForceHTTP1.

3. Klasifikasi blokir

Empat situasi berbeda bersembunyi di balik HTTP 403, dan masing-masing butuh tindakan berbeda. Classify membedakannya dari header + body, tanpa request tambahan:

Kind Penanda Tindakan
CF_CHALLENGE cf-mitigated: challenge, _cf_chl_opt, Just a moment butuh browser yang mengeksekusi JS
CF_BLOCK_1010 Error 1010 browser signature dibanned: pakai profil TLS, bukan UA palsu
CF_BLOCK_1009 Error 1009 blokir negara: perlu proxy lokal
CF_BLOCK_1015 Error 1015 rate limited: cooldown
CF_BLOCK_1020 Error 1020 firewall rule menolak ASN/datacenter
WAF_GENERIC x-sucuri-id, x-datadome, x-akamai-request-id, x-iinfo WAF pihak ketiga
b := unblock.Classify(resp)
if b != nil {
    log.Printf("diblokir: %s (ray %s) — %s", b.Kind, b.RayID, b.Hint)
}
if b.Challenge() {
    // satu-satunya kategori yang bisa diselesaikan browser
}

403 dari origin (forbidden biasa, halaman login) sengaja tidak dianggap blokir — itu bukan urusan anti-bot.

Cooldown per host

Blokir Cloudflare bersifat sticky: mengulang request ke host yang sama dalam waktu singkat justru menaikkan level blokir. Cooldown memberi jarak:

cd := unblock.NewCooldown(6 * time.Hour)
host := unblock.HostOf(url)

if !cd.Allow(host) {
    return fmt.Errorf("cooldown %s: %s lagi", host, cd.Remaining(host))
}
resp, err := c.Get(ctx, url)
if b := resp.Blocked(); b != nil {
    cd.Mark(host)
}

Yang TIDAK dikerjakan

Tidak ada eksekusi JavaScript. Challenge Cloudflare managed (Turnstile, halaman "Checking your browser" yang menghitung token) tetap tidak bisa dilewati — fingerprint hanya menang melawan aturan yang murni berbasis TLS/h2.

Paket ini memberi tahu bahwa itu yang terjadi (CF_CHALLENGE), bukan berpura-pura bisa menyelesaikannya. Untuk itu kamu butuh browser sungguhan: FlareSolverr, go-rod, atau browser pengguna sendiri.

Juga di luar cakupan: IP reputation dan perilaku. IP datacenter tetap sering diblok walau sidik jarinya sempurna — pakai Options.ProxyURL untuk keluar dari IP tersebut.

Install

go get github.com/nyelonong/unblock

Butuh Go 1.27+ (mengikuti tls-client).

API

func New(opts Options) *Client
func (c *Client) Get(ctx context.Context, url string) (*Response, error)
func (c *Client) Head(ctx context.Context, url string) (*Response, error)
func (c *Client) Do(ctx context.Context, method, url string) (*Response, error)

func Classify(r *Response) *Block
func HostOf(rawURL string) string
func NewCooldown(window time.Duration) *Cooldown

Options: Profile, ProxyURL, Timeout, MaxBody, Headers, ForceHTTP1.

Response: StatusCode, Header, Body, FinalURL, Attempts, HTTP1, plus Get(header) dan Blocked().

Test

go test ./...

Mencakup server sungguhan (httptest), batas body, header kustom, ForceHTTP1, 9 varian klasifikasi blokir, dan perilaku cooldown.

Lisensi

MIT — lihat LICENSE.

Documentation

Overview

Package unblock adalah HTTP client Go yang menyerupai browser (ClientHello dan HTTP/2 fingerprint Chrome) dan tahu kapan dirinya diblokir.

Tiga hal yang ditangani:

  1. TLS/JA3 dan HTTP/2 fingerprint — lewat bogdanfinn/tls-client, sehingga aturan WAF berbasis fingerprint tidak menolak request.
  2. Fallback HTTP/1.1 otomatis ketika server menolak HTTP/2 (RST_STREAM), dengan fingerprint TLS yang sama.
  3. Klasifikasi blokir — Cloudflare challenge, error 1009/1010/1015/1020, dan WAF pihak ketiga dibedakan satu sama lain, karena masing-masing butuh tindakan berbeda.

Paket ini TIDAK mengeksekusi JavaScript. Challenge Cloudflare yang butuh JS tetap tidak bisa dilewati di sini; Classify hanya memberi tahu bahwa itu yang terjadi.

Index

Examples

Constants

View Source
const DefaultCooldownWindow = 6 * time.Hour

DefaultCooldownWindow dipakai bila NewCooldown menerima durasi nol.

View Source
const DefaultMaxBody = 5 << 20

DefaultMaxBody adalah batas ukuran body (5 MiB).

View Source
const DefaultTimeout = 20 * time.Second

DefaultTimeout adalah batas waktu per percobaan bila Options.Timeout kosong.

Variables

View Source
var ErrNoBody = errors.New("unblock: empty body")

ErrNoBody dipakai saat respons tidak membawa data yang diharapkan.

Functions

func HostOf

func HostOf(raw string) string

HostOf mengambil host (tanpa port) dari URL; mengembalikan string apa adanya bila URL tidak bisa diparse.

Types

type Block

type Block struct {
	// Kind adalah kategori: CF_CHALLENGE, CF_BLOCK_1009, CF_BLOCK_1010,
	// CF_BLOCK_1015, CF_BLOCK_1020, CF_BLOCK_GENERIC, WAF_GENERIC.
	Kind string `json:"kind"`
	// Code adalah kode error Cloudflare bila ada (mis. "1020").
	Code string `json:"code,omitempty"`
	// RayID berguna saat menghubungi support penyedia WAF atau pemilik situs.
	RayID string `json:"ray_id,omitempty"`
	// Server adalah nilai header Server.
	Server string `json:"server,omitempty"`
	// Hint menyarankan tindakan berikutnya.
	Hint string `json:"hint,omitempty"`
}

Block menjelaskan satu peristiwa blokir.

func Classify

func Classify(r *Response) *Block

Classify memeriksa apakah respons merupakan blokir WAF/anti-bot. Mengembalikan nil bila bukan blokir.

Empat situasi berbeda bersembunyi di balik HTTP 403, dan masing-masing butuh respons yang berbeda — jangan pernah memperlakukannya sebagai satu kasus.

Example

Contoh membedakan jenis blokir.

package main

import (
	"fmt"
	"net/http"

	"github.com/nyelonong/unblock"
)

func main() {
	h := http.Header{}
	h.Set("Server", "cloudflare")
	h.Set("Cf-Ray", "8a1b2c3d-JKT")

	resp := &unblock.Response{
		StatusCode: 403,
		Header:     h,
		Body:       []byte("<title>Attention Required!</title><h2>Error 1020</h2>"),
	}

	b := unblock.Classify(resp)
	fmt.Println(b.Kind, b.Code, b.Challenge())
	fmt.Println(b.Hint)

}
Output:
CF_BLOCK_1020 1020 false
firewall rule menolak ASN/datacenter: perlu proxy residensial

func (*Block) Challenge

func (b *Block) Challenge() bool

Challenge melaporkan apakah blokir ini bisa diselesaikan oleh browser yang mengeksekusi JavaScript (FlareSolverr, go-rod, atau browser pengguna).

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client adalah fetcher dengan fingerprint browser.

func New

func New(opts Options) *Client

New membuat Client. Konfigurasi yang tidak valid tidak membuat New gagal: Client akan memakai fallback tanpa impersonation (net/http) supaya pemanggil tidak perlu menangani error di jalur inisialisasi.

func (*Client) Do

func (c *Client) Do(ctx context.Context, method, url string) (*Response, error)

Do menjalankan request dan mengembalikan body lengkap.

Bila percobaan pertama gagal karena masalah HTTP/2 (RST_STREAM, INTERNAL_ERROR), request diulang sekali lewat HTTP/1.1 dengan fingerprint TLS yang sama. Separuh budget waktu disisakan untuk percobaan kedua.

func (*Client) Get

func (c *Client) Get(ctx context.Context, url string) (*Response, error)

Get mengambil URL. Request mengikuti redirect secara default.

Example

Contoh mengambil halaman dengan fingerprint browser.

package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"time"

	"github.com/nyelonong/unblock"
)

func main() {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte("<html>halo</html>"))
	}))
	defer srv.Close()

	c := unblock.New(unblock.Options{Profile: "chrome_133", Timeout: 5 * time.Second})

	resp, err := c.Get(context.Background(), srv.URL)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(resp.StatusCode, len(resp.Body) > 0, resp.Blocked() == nil)

}
Output:
200 true true

func (*Client) Head

func (c *Client) Head(ctx context.Context, url string) (*Response, error)

Head mengirim HEAD. Sebagian server menolak HEAD; pemanggil yang perlu fallback ke Get harus melakukannya sendiri.

type Cooldown

type Cooldown struct {
	// contains filtered or unexported fields
}

Cooldown mencatat host yang baru saja memblokir kita.

Blokir Cloudflare bersifat "sticky": mengulang request ke host yang sama dalam waktu singkat justru menaikkan level blokir (challenge menjadi block). Cooldown memberi jarak sebelum host itu dicoba lagi.

Example

Contoh memakai cooldown per host.

package main

import (
	"fmt"
	"time"

	"github.com/nyelonong/unblock"
)

func main() {
	cd := unblock.NewCooldown(time.Hour)
	host := unblock.HostOf("https://contoh.id/halaman")

	fmt.Println(cd.Allow(host))
	cd.Mark(host)
	fmt.Println(cd.Allow(host))

}
Output:
true
false

func NewCooldown

func NewCooldown(window time.Duration) *Cooldown

NewCooldown membuat Cooldown dengan jendela tertentu. Nol berarti DefaultCooldownWindow.

func (*Cooldown) Allow

func (c *Cooldown) Allow(host string) bool

Allow melaporkan apakah host boleh dicoba sekarang.

func (*Cooldown) Mark

func (c *Cooldown) Mark(host string)

Mark menandai host agar tidak dicoba selama jendela cooldown.

func (*Cooldown) Remaining

func (c *Cooldown) Remaining(host string) time.Duration

Remaining mengembalikan sisa waktu tunggu untuk host (0 bila boleh dicoba).

func (*Cooldown) Reset

func (c *Cooldown) Reset(host string)

Reset menghapus penanda cooldown untuk host.

type Options

type Options struct {
	// Profile adalah nama preset TLS, mis. "chrome_133" atau "firefox_135".
	// Kosong berarti profil Chrome default paket (versi terbaru yang didukung).
	// Lihat profiles.MappedTLSClients untuk daftar lengkap.
	Profile string

	// ProxyURL opsional: "http://127.0.0.1:8080" atau "socks5://10.0.0.2:1080".
	ProxyURL string

	// Timeout per percobaan. Kosong berarti DefaultTimeout.
	Timeout time.Duration

	// MaxBody membatasi jumlah byte body yang dibaca. Kosong berarti DefaultMaxBody.
	MaxBody int64

	// Headers ditambahkan ke setiap request. Header yang sudah diatur profil
	// TLS (user-agent, accept, dsb) akan ditimpa oleh nilai di sini.
	Headers map[string]string

	// ForceHTTP1 mematikan negosiasi HTTP/2 sepenuhnya. Berguna untuk server
	// yang men-tarpit atau menolak h2 secara konsisten.
	ForceHTTP1 bool
}

Options mengatur perilaku Client.

type Response

type Response struct {
	StatusCode int
	Header     http.Header
	Body       []byte
	FinalURL   string

	// Attempts berisi 1 untuk percobaan pertama, 2 bila fallback HTTP/1.1 dipakai.
	Attempts int
	// HTTP1 bernilai true bila respons datang dari jalur fallback HTTP/1.1.
	HTTP1 bool
}

Response adalah respons yang sudah dibaca penuh (dibatasi MaxBody).

func (*Response) Blocked

func (r *Response) Blocked() *Block

Blocked mengembalikan detail blokir, atau nil bila respons bukan blokir.

func (*Response) Get

func (r *Response) Get(name string) string

Get mengambil URL dengan metode GET.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL