Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: retryable HTTP requests #245

Merged
merged 8 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ Here are all the options it supports.
| --only-cc `<AA>,<BB>` | Only show specific country code (comma separated). |
| -t, --timeout | Max. time allowed for proxy server/check (default: 30s). |
| -r, --rotate `<AFTER>` | Rotate proxy IP for every `AFTER` request (default: 1). |
| --max-retries `<N>` | Max. retries for failed HTTP requests (default: 3). |
| -m, --method `<METHOD>` | Rotation method (sequent/random) (default: sequent). |
| -s, --sync | Sync will wait for the previous request to complete. |
| -v, --verbose | Dump HTTP request/responses or show died proxy on check. |
Expand Down
29 changes: 15 additions & 14 deletions common/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ type Options struct {
Result *os.File
Timeout time.Duration

Address string
Auth string
CC string
Check bool
Countries []string
Daemon bool
File string
Goroutine int
Method string
Output string
Rotate int
Sync bool
Verbose bool
Watch bool
Address string
Auth string
CC string
Check bool
Countries []string
Daemon bool
File string
Goroutine int
Method string
Output string
Rotate int
Sync bool
Verbose bool
Watch bool
MaxRetries int
}
1 change: 1 addition & 0 deletions common/vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Options:
-d, --daemon Daemonize proxy server
-m, --method <METHOD> Rotation method (sequent/random) (default: sequent)
-r, --rotate <N> Rotate proxy IP after N request (default: 1)
--max-retries <N> Max. retries for failed HTTP requests (default: 3)
-s, --sync Syncrounus mode
-w, --watch Watch proxy file, live-reload from changes

Expand Down
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,25 @@ require (
)

require (
github.com/fatih/color v1.13.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/go-github v17.0.0+incompatible // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-version v1.3.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.1.0 // indirect
golang.org/x/text v0.4.0 // indirect
)
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy0
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
Expand All @@ -30,6 +32,10 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI=
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw=
github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/henvic/httpretty v0.1.2 h1:EQo556sO0xeXAjP10eB+BZARMuvkdGqtfeS4Ntjvkiw=
Expand Down Expand Up @@ -60,6 +66,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mbndr/logo v0.0.1 h1:qWCRjgCP9tao2jGsYiGXceEUQEoILRFfbZaf+o4r868=
github.com/mbndr/logo v0.0.1/go.mod h1:eD5FeNoALymRumX35fnzCm0RtoS9HQ+jDC+GGmE6/KM=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
Expand Down Expand Up @@ -126,6 +134,9 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
Expand Down
2 changes: 2 additions & 0 deletions internal/runner/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ func Options() *common.Options {
flag.IntVar(&opt.Goroutine, "g", 50, "")
flag.IntVar(&opt.Goroutine, "goroutine", 50, "")

flag.IntVar(&opt.MaxRetries, "max-retries", 3, "")

flag.Usage = func() {
showBanner()
showUsage()
Expand Down
21 changes: 19 additions & 2 deletions internal/server/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"

"github.com/elazarl/goproxy"
"github.com/hashicorp/go-retryablehttp"
"github.com/kitabisa/mubeng/common"
"github.com/kitabisa/mubeng/pkg/helper"
"github.com/kitabisa/mubeng/pkg/mubeng"
Expand Down Expand Up @@ -60,7 +61,7 @@ func (p *Proxy) onRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Reque
Transport: tr,
}

client, err := proxy.New(req)
client, err := proxy.New(r)
if err != nil {
resChan <- err
return
Expand All @@ -71,7 +72,23 @@ func (p *Proxy) onRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Reque
client.Transport = dump.RoundTripper(tr)
}

resp, err := client.Do(req)
retryablehttpClient := mubeng.ToRetryableHTTPClient(client)
retryablehttpClient.RetryMax = p.Options.MaxRetries
retryablehttpClient.RetryWaitMin = client.Timeout
retryablehttpClient.RetryWaitMax = client.Timeout
retryablehttpClient.Logger = ReleveledLogo{
Logger: log,
Request: r,
Verbose: p.Options.Verbose,
}

retryablehttpRequest, err := retryablehttp.FromRequest(r)
if err != nil {
resChan <- err
return
}

resp, err := retryablehttpClient.Do(retryablehttpRequest)
if err != nil {
resChan <- err
return
Expand Down
71 changes: 71 additions & 0 deletions internal/server/releveledlogo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package server

import (
"fmt"
"net/http"

"github.com/mbndr/logo"
)

// repo:hashicorp/go-retryablehttp /v\.(Error|Info|Debug|Warn)\("/
type ReleveledLogo struct {
*logo.Logger
*http.Request

Verbose bool
}

func (l ReleveledLogo) toArgs(msg string, keysAndValues ...interface{}) []interface{} {
args := []interface{}{msg}

for i := 0; i < len(keysAndValues); i += 2 {
if i+1 < len(keysAndValues) {
key := keysAndValues[i]
value := keysAndValues[i+1]

switch key {
case "request", "timeout":
continue
case "error":
return []interface{}{value}
case "remaining":
value = fmt.Sprintf("%d", value)
}

args = append(args, fmt.Sprintf(" [%v=%q]", key, value))
}
}

return args
}

func (l ReleveledLogo) Error(msg string, keysAndValues ...interface{}) {
if l.Verbose {
return
}

args := l.toArgs(msg, keysAndValues...)
l.Logger.Error(args...)
}

func (l ReleveledLogo) Info(msg string, keysAndValues ...interface{}) {
args := l.toArgs(msg, keysAndValues...)
l.Logger.Info(args...)
}

func (l ReleveledLogo) Debug(msg string, keysAndValues ...interface{}) {
switch msg {
case "performing request":
return
case "retrying request":
msg = fmt.Sprintf("%s Retrying %s %s", l.Request.RemoteAddr, l.Request.Method, l.Request.URL)
}

args := l.toArgs(msg, keysAndValues...)
l.Logger.Debug(args...)
}

func (l ReleveledLogo) Warn(msg string, keysAndValues ...interface{}) {
args := l.toArgs(msg, keysAndValues...)
l.Logger.Warn(args...)
}
12 changes: 11 additions & 1 deletion pkg/mubeng/mubeng.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"net/http"
"net/url"
"strings"

"github.com/hashicorp/go-retryablehttp"
)

// New define HTTP client & request of http.Request itself.
// New define HTTP client request of the [http.Request] itself.
//
// also removes Hop-by-hop headers when it is sent to backend (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html),
// then add X-Forwarded-For header value with the IP address value of rotator proxy IP.
Expand Down Expand Up @@ -38,3 +40,11 @@ func (proxy *Proxy) New(req *http.Request) (*http.Client, error) {

return client, nil
}

// ToRetryableHTTPClient converts standard [http.Client] to [retryablehttp.Client]
func ToRetryableHTTPClient(client *http.Client) *retryablehttp.Client {
retryablehttpClient := retryablehttp.NewClient()
retryablehttpClient.HTTPClient = client

return retryablehttpClient
}
Loading