httpclient

package
v0.3.1-alpha Latest Latest
Warning

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

Go to latest
Published: Apr 30, 2026 License: MIT Imports: 14 Imported by: 0

README

HTTP Client Package

This package provides a unified HTTP client factory for all scrapers, eliminating code duplication and providing consistent proxy, retry, and header handling.

Overview

The package implements three patterns:

  • Builder Pattern: For custom HTTP client configuration
  • Factory Functions: Quick client creation for common use cases
  • FlareSolverr Integration: For sites requiring browser challenge bypass

Usage Examples

Simple Scrapers (No ProxyProfile needed)

For scrapers that don't need browser capabilities:

import (
    "github.com/go-resty/resty/v2"
    "github.com/javinizer/javinizer-go/internal/httpclient"
)

func NewHTTPClient(cfg *config.ScraperSettings, globalProxy *config.ProxyConfig, globalFlareSolverr config.FlareSolverrConfig) (*resty.Client, error) {
    return httpclient.FromScraperSettings(cfg, globalProxy, globalFlareSolverr,
        httpclient.WithHeaders(httpclient.CombineHeaders(
            httpclient.StandardHTMLHeaders(),
            httpclient.UserAgentHeader(cfg.UserAgent),
        )),
    ).BuildClient()
}

Returns: (*resty.Client, error)

FlareSolverr Scrapers (Browser bypass)

For scrapers that need FlareSolverr to bypass Cloudflare/browser challenges:

import (
    "github.com/go-resty/resty/v2"
    "github.com/javinizer/javinizer-go/internal/httpclient"
)

func NewHTTPClient(cfg *config.ScraperSettings, globalProxy *config.ProxyConfig, globalFlareSolverr config.FlareSolverrConfig) (*resty.Client, *httpclient.FlareSolverr, error) {
    builder := httpclient.FromScraperSettings(cfg, globalProxy, globalFlareSolverr,
        httpclient.WithHeaders(httpclient.StandardHTMLHeaders()),
        httpclient.WithHeaders(httpclient.UserAgentHeader(cfg.UserAgent)),
    )
    return builder.BuildWithFlareSolverr()
}

Returns: (*resty.Client, *httpclient.FlareSolverr, error)

Browser Scrapers (Need ProxyProfile)

For scrapers that need to pass proxy configuration to browser automation:

import (
    "github.com/go-resty/resty/v2"
    "github.com/javinizer/javinizer-go/internal/httpclient"
    "github.com/javinizer/javinizer-go/internal/config"
)

func NewHTTPClient(cfg *config.ScraperSettings, globalProxy *config.ProxyConfig, globalFlareSolverr config.FlareSolverrConfig) (*resty.Client, *config.ProxyProfile, error) {
    return httpclient.FromScraperSettings(cfg, globalProxy, globalFlareSolverr,
        httpclient.WithHeaders(httpclient.CombineHeaders(
            httpclient.DMMHeaders(),
            httpclient.UserAgentHeader(cfg.UserAgent),
        )),
    ).BuildWithProxy()
}

Returns: (*resty.Client, *config.ProxyProfile, error)

Header Presets

The package provides pre-built header presets:

Preset Description
StandardHTMLHeaders() Standard browser headers for HTML pages
JSONAPIHeaders() Headers for JSON API requests
JapaneseLanguageHeaders() HTML headers with Japanese language preference
DMMHeaders() DMM-specific headers with cookies
R18DevHeaders() R18Dev API headers
RefererHeader(url) Creates a Referer header
UserAgentHeader(ua) User-Agent header with default fallback
Combining Headers
headers := httpclient.CombineHeaders(
    httpclient.StandardHTMLHeaders(),
    httpclient.UserAgentHeader("custom-agent"),
    httpclient.RefererHeader("https://example.com"),
)
Merging Cookies
existingCookies := map[string]string{"session": "abc123"}
newCookies := map[string]string{"token": "xyz789"}
cookieHeader := httpclient.MergeCookieHeader(existingCookies, newCookies)
// Result: "session=abc123; token=xyz789"

Functional Options

The builder supports these options:

Option Description
WithTimeout(d) Set request timeout (default: 30s)
WithRetryCount(n) Set retry count (default: 3)
WithGlobalProxy(cfg) Set global proxy configuration
WithGlobalFlareSolverr(cfg) Set global FlareSolverr configuration
WithScraperProxy(cfg) Set scraper-specific proxy override
WithFlareSolverr(enabled) Enable FlareSolverr for this scraper
WithHeader(k, v) Add a single header
WithHeaders(m) Add multiple headers
WithCookies(m) Add cookies
WithReturnProxyProfile() Request ProxyProfile in response

Builder Methods

Method Returns Description
BuildClient() (*resty.Client, error) Simple client, no proxy profile
BuildWithProxy() (*resty.Client, *ProxyProfile, error) Client with proxy profile for browser use
BuildWithFlareSolverr() (*resty.Client, *FlareSolverr, error) Client with FlareSolverr instance
Build() (*ScraperClient, error) Full ScraperClient with all components

FlareSolverr Usage

// Create FlareSolverr-enabled client
client, flare, err := httpclient.FromScraperSettings(cfg, globalProxy, globalFlareSolverr,
    httpclient.WithHeaders(httpclient.StandardHTMLHeaders()),
    httpclient.WithFlareSolverr(true),
).BuildWithFlareSolverr()

// Make request through FlareSolverr
resp, err := flare.RequestGet(ctx, "https://example.com", nil)

// The HTML content is in resp.Solution.Response
html := resp.Solution.Response

Proxy Support

The package automatically handles:

  • HTTP/HTTPS proxies
  • SOCKS5 proxies
  • Proxy authentication
  • Per-scraper proxy overrides

Proxy priority: ScraperSettings.Proxy > Global Proxy > None

Migration Guide

Before (Duplicated Code)
func NewHTTPClient(cfg *config.Config) (*resty.Client, error) {
    client := resty.New()
    client.SetTimeout(30 * time.Second)
    client.SetRetryCount(3)
    
    // Handle proxy
    if cfg.Proxy.Enabled {
        // 40+ lines of proxy setup...
    }
    
    // Set headers
    client.SetHeaders(map[string]string{
        "Accept": "text/html...",
        // More headers...
    })
    
    return client, nil
}
After (Using Factory)
func NewHTTPClient(cfg *config.ScraperSettings, globalProxy *config.ProxyConfig, globalFlareSolverr config.FlareSolverrConfig) (*resty.Client, error) {
    return httpclient.FromScraperSettings(cfg, globalProxy, globalFlareSolverr,
        httpclient.WithHeaders(httpclient.StandardHTMLHeaders()),
    ).BuildClient()
}

Architecture

┌─────────────────────────────────────────────────────────────┐
│                      Scraper Packages                        │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐           │
│  │   DMM   │ │ JavBus  │ │ JavDB   │ │  FC2    │ ...       │
│  └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘           │
│       │           │           │           │                 │
│       └───────────┴───────────┴───────────┘                 │
│                       │                                     │
│                       ▼                                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │           internal/httpclient                         │   │
│  │  ┌──────────────┐  ┌──────────────┐  ┌───────────┐  │   │
│  │  │   Builder    │  │   Factory    │  │ FlareSol. │  │   │
│  │  │   builder.go │  │  factory.go  │  │(embedded) │  │   │
│  │  └──────────────┘  └──────────────┘  └───────────┘  │   │
│  │  ┌──────────────┐  ┌──────────────┐                  │   │
│  │  │   Presets    │  │   Client     │                  │   │
│  │  │  presets.go  │  │  client.go   │                  │   │
│  │  └──────────────┘  └──────────────┘                  │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

Testing

# Run package tests
go test ./internal/httpclient/...

# Run with race detector
go test -race ./internal/httpclient/...

Documentation

Index

Constants

View Source
const (
	DefaultTimeout    = 30 * time.Second
	DefaultRetryCount = 3
)

Variables

This section is empty.

Functions

func CombineHeaders

func CombineHeaders(presets ...map[string]string) map[string]string

func DMMHeaders

func DMMHeaders() map[string]string

func DrainAndClose

func DrainAndClose(body io.ReadCloser) error

func IsValidCookieName

func IsValidCookieName(name string) bool

IsValidCookieName validates a cookie name against RFC 6265 token rules. Cookie names must be valid tokens: alphanumeric, dash, underscore, and a few special chars.

func JSONAPIHeaders

func JSONAPIHeaders() map[string]string

func JapaneseLanguageHeaders

func JapaneseLanguageHeaders() map[string]string

func MergeCookieHeader

func MergeCookieHeader(existing, new map[string]string) string

func NewHTTPClient

func NewHTTPClient(proxyProfile *config.ProxyProfile, timeout time.Duration) (*http.Client, error)

NewHTTPClient creates a standard http.Client with proxy support

func NewRestyClient

func NewRestyClient(proxyProfile *config.ProxyProfile, timeout time.Duration, retries int) (*resty.Client, error)

NewRestyClient creates a resty.Client with proxy support

func NewRestyClientNoProxy

func NewRestyClientNoProxy(timeout time.Duration, retries int) *resty.Client

NewRestyClientNoProxy creates a resty.Client that explicitly bypasses environment proxy variables by using a no-proxy transport.

func NewScraperHTTPClient

func NewScraperHTTPClient(cfg *config.ScraperSettings, globalProxy *config.ProxyConfig, globalFlareSolverr config.FlareSolverrConfig, opts ...ScraperHTTPClientOption) (*resty.Client, error)

func NewTransport

func NewTransport(proxyProfile *config.ProxyProfile) (*http.Transport, error)

NewTransport creates an http.Transport with optional proxy support

func R18DevHeaders

func R18DevHeaders() map[string]string

func RefererHeader

func RefererHeader(url string) map[string]string

func SanitizeCookieValue

func SanitizeCookieValue(value string) string

SanitizeCookieValue removes characters forbidden in RFC 6265 cookie values. Prevents header injection and ensures parsing stability.

func SanitizeProxyURL

func SanitizeProxyURL(proxyURL string) string

SanitizeProxyURL removes credentials from proxy URL for safe logging

func StandardHTMLHeaders

func StandardHTMLHeaders() map[string]string

func UserAgentHeader

func UserAgentHeader(ua string) map[string]string

Types

type FlareSolverr

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

FlareSolverr represents the FlareSolverr client

func GetFlareSolverrFromClient

func GetFlareSolverrFromClient(client *resty.Client) (*FlareSolverr, bool)

GetFlareSolverrFromClient extracts FlareSolverr instance from resty client context Note: This is a helper for scrapers that need to access FlareSolverr The FlareSolverr instance is typically stored separately and passed to scrapers

func NewFlareSolverr

func NewFlareSolverr(cfg *config.FlareSolverrConfig) (*FlareSolverr, error)

NewFlareSolverr creates a new FlareSolverr client

func NewRestyClientWithFlareSolverr

func NewRestyClientWithFlareSolverr(proxyProfile *config.ProxyProfile, flaresolverrCfg config.FlareSolverrConfig, timeout time.Duration, retries int) (*resty.Client, *FlareSolverr, error)

NewRestyClientWithFlareSolverr creates a resty.Client with optional FlareSolverr support Note: FlareSolverr config is passed separately since it's at ScrapersConfig.FlareSolverr (top-level), not inside ProxyConfig (which only holds proxy settings).

func (*FlareSolverr) Close

func (fs *FlareSolverr) Close() error

Close cleans up resources held by the FlareSolverr. Currently a no-op since FlareSolverr uses stateless HTTP requests, but provides a cleanup hook for future resource management.

func (*FlareSolverr) CreateSession

func (fs *FlareSolverr) CreateSession() (string, error)

CreateSession creates a new FlareSolverr session for cookie persistence

func (*FlareSolverr) DestroySession

func (fs *FlareSolverr) DestroySession(sessionID string) error

DestroySession destroys a FlareSolverr session via HTTP and removes it from local cache.

func (*FlareSolverr) ResolveURL

func (fs *FlareSolverr) ResolveURL(targetURL string) (string, []http.Cookie, error)

ResolveURL resolves a URL through FlareSolverr, returning HTML content and cookies. The mutex is held for the entire operation to ensure session reuse is safe from concurrent reset calls.

func (*FlareSolverr) ResolveURLWithSession

func (fs *FlareSolverr) ResolveURLWithSession(targetURL, sessionID string) (string, []http.Cookie, error)

ResolveURLWithSession resolves a URL using a specific session

type FlareSolverrProxy

type FlareSolverrProxy struct {
	URL      string `json:"url"`
	Username string `json:"username,omitempty"`
	Password string `json:"password,omitempty"`
}

FlareSolverrProxy represents a per-request proxy configuration passed to FlareSolverr. This is used for the target URL request made by FlareSolverr, not for calls to FlareSolverr itself.

type FlareSolverrRequest

type FlareSolverrRequest struct {
	Cmd               string             `json:"cmd"`                           // "request.get" or "sessions.create"
	URL               string             `json:"url"`                           // Target URL
	MaxTimeout        int                `json:"maxTimeout"`                    // Timeout in milliseconds (FlareSolverr expects ms)
	Session           string             `json:"session"`                       // Optional: reuse existing session
	SessionTTLMinutes int                `json:"session_ttl_minutes,omitempty"` // Optional: rotate existing session when older than TTL
	Proxy             *FlareSolverrProxy `json:"proxy,omitempty"`               // Optional: proxy for target URL request
}

FlareSolverrRequest represents a request to FlareSolverr

type FlareSolverrResponse

type FlareSolverrResponse struct {
	Status   string `json:"status"`
	Message  string `json:"message"`
	Solution struct {
		Response string `json:"response"`
		Cookies  []struct {
			Name  string `json:"name"`
			Value string `json:"value"`
		} `json:"cookies"`
		UserAgent string `json:"userAgent"`
	} `json:"solution"`
	Session string `json:"session"`
}

FlareSolverrResponse represents a FlareSolverr response

type FlareSolverrSession

type FlareSolverrSession struct {
	Token   string
	Created time.Time
	URLs    []string
	Cookies []http.Cookie
}

FlareSolverrSession represents a FlareSolverr session

type HTTPClient

type HTTPClient interface {
	// Do executes an HTTP request and returns the response
	Do(req *http.Request) (*http.Response, error)
}

HTTPClient defines the interface for HTTP operations This allows for easy mocking in tests

type ScraperClient

type ScraperClient struct {
	Client       *resty.Client
	FlareSolverr *FlareSolverr
	ProxyProfile *config.ProxyProfile
}

type ScraperClientBuilder

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

func FromScraperSettings

func FromScraperSettings(settings *config.ScraperSettings, globalProxy *config.ProxyConfig, globalFlareSolverr config.FlareSolverrConfig, opts ...ScraperOption) *ScraperClientBuilder

func NewScraperClientBuilder

func NewScraperClientBuilder() *ScraperClientBuilder

func (*ScraperClientBuilder) Apply

func (*ScraperClientBuilder) Build

func (b *ScraperClientBuilder) Build() (*ScraperClient, error)

func (*ScraperClientBuilder) BuildClient

func (b *ScraperClientBuilder) BuildClient() (*resty.Client, error)

func (*ScraperClientBuilder) BuildWithFlareSolverr

func (b *ScraperClientBuilder) BuildWithFlareSolverr() (*resty.Client, *FlareSolverr, error)

func (*ScraperClientBuilder) BuildWithProxy

func (b *ScraperClientBuilder) BuildWithProxy() (*resty.Client, *config.ProxyProfile, error)

type ScraperClientResult

type ScraperClientResult struct {
	Client       *resty.Client
	ProxyProfile *config.ProxyProfile
	ProxyEnabled bool
}

func InitScraperClient

func InitScraperClient(settings *config.ScraperSettings, globalProxy *config.ProxyConfig, globalFlareSolverr config.FlareSolverrConfig, opts ...ScraperHTTPClientOption) *ScraperClientResult

type ScraperHTTPClientOption

type ScraperHTTPClientOption func(*scraperHTTPConfig)

func WithProxyProfile

func WithProxyProfile() ScraperHTTPClientOption

func WithScraperCookies

func WithScraperCookies(cookies map[string]string) ScraperHTTPClientOption

func WithScraperHeaders

func WithScraperHeaders(headers map[string]string) ScraperHTTPClientOption

type ScraperOption

type ScraperOption func(*scraperConfig)

func WithCookies

func WithCookies(cookies map[string]string) ScraperOption

func WithFlareSolverr

func WithFlareSolverr(enabled bool) ScraperOption

func WithGlobalFlareSolverr

func WithGlobalFlareSolverr(cfg config.FlareSolverrConfig) ScraperOption

func WithGlobalProxy

func WithGlobalProxy(global config.ProxyConfig) ScraperOption

func WithHeader

func WithHeader(key, value string) ScraperOption

func WithHeaders

func WithHeaders(headers map[string]string) ScraperOption

func WithProxyProfileReturn

func WithProxyProfileReturn(enabled bool) ScraperOption

func WithRetryCount

func WithRetryCount(count int) ScraperOption

func WithScraperProxy

func WithScraperProxy(scraper *config.ProxyConfig) ScraperOption

func WithTimeout

func WithTimeout(timeout time.Duration) ScraperOption

Jump to

Keyboard shortcuts

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