Skip to content

Commit a761061

Browse files
committed
feat: add total bandwidth limit support
1 parent d14a9b6 commit a761061

File tree

4 files changed

+54
-1
lines changed

4 files changed

+54
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Usage:
1111
git-proxy [flags]
1212

1313
Flags:
14+
-l, --bandwidth-limit int set total bandwidth limit (MB/s), 0 as no limit (default 0)
1415
-b, --blacklist-path string set repository blacklist (default "blacklist.txt")
1516
--disable-color disable color output
1617
-d, --domain-list-path string set accept domain (default "domainlist.txt")

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23.3
55
require (
66
github.com/go-chi/chi/v5 v5.2.0
77
github.com/go-chi/render v1.0.3
8+
github.com/juju/ratelimit v1.0.2
89
github.com/sagernet/fswatch v0.1.1
910
github.com/sagernet/sing v0.5.1
1011
github.com/sagernet/sing-box v1.10.6
@@ -15,6 +16,7 @@ require (
1516
github.com/ajg/form v1.5.1 // indirect
1617
github.com/fsnotify/fsnotify v1.7.0 // indirect
1718
github.com/inconshreveable/mousetrap v1.1.0 // indirect
19+
github.com/kr/text v0.2.0 // indirect
1820
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
1921
github.com/miekg/dns v1.1.62 // indirect
2022
github.com/sagernet/sing-dns v0.3.0 // indirect

go.sum

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
22
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
33
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
4+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
45
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
56
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
67
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
@@ -15,10 +16,16 @@ github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5X
1516
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
1617
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
1718
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
19+
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
20+
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
21+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
22+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
1823
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
1924
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
2025
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
2126
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
27+
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
28+
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
2229
github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
2330
github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0=
2431
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -63,5 +70,7 @@ golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
6370
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
6471
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
6572
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
73+
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
74+
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
6675
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
6776
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

main.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/go-chi/chi/v5"
1919
"github.com/go-chi/chi/v5/middleware"
2020
"github.com/go-chi/render"
21+
R "github.com/juju/ratelimit"
2122
"github.com/sagernet/fswatch"
2223
L "github.com/sagernet/sing-box/log"
2324
"github.com/sagernet/sing/common"
@@ -50,8 +51,11 @@ var (
5051
runningPort int
5152
domainListPath string
5253
blacklistPath string
54+
bandwidthLimit int
5355
)
5456

57+
var BandwidthLimiter *R.Bucket
58+
5559
var Blacklist []RepoInfo
5660

5761
var AcceptDomain = []string{
@@ -76,6 +80,7 @@ func init() {
7680
command.PersistentFlags().IntVarP(&runningPort, "running-port", "p", 30000, "disable color output")
7781
command.PersistentFlags().StringVarP(&domainListPath, "domain-list-path", "d", "domainlist.txt", "set accept domain")
7882
command.PersistentFlags().StringVarP(&blacklistPath, "blacklist-path", "b", "blacklist.txt", "set repository blacklist")
83+
command.PersistentFlags().IntVarP(&bandwidthLimit, "bandwidth-limit", "l", 0, "set total bandwidth limit (MB/s), 0 as no limit")
7984
}
8085

8186
func main() {
@@ -101,6 +106,10 @@ func newError(msg string) *HTTPError {
101106
}
102107

103108
func run(*cobra.Command, []string) {
109+
if bandwidthLimit > 0 {
110+
BandwidthLimiter = R.NewBucketWithRate(float64(bandwidthLimit*1024*1024), int64(bandwidthLimit*1024*1024))
111+
log.Info("Bandwidth limit is set as ", bandwidthLimit, "MB/s")
112+
}
104113
if watcher, err := loadDomainList(); err == nil {
105114
err = watcher.Start()
106115
if err == nil {
@@ -466,6 +475,34 @@ func responseWithRedirect(URL *url.URL) http.Handler {
466475
})
467476
}
468477

478+
var _ io.Reader = (*LimitReader)(nil)
479+
480+
type LimitReader struct {
481+
reader io.Reader
482+
bucket *R.Bucket
483+
}
484+
485+
func NewLimitReader(reader io.Reader, bucket *R.Bucket) *LimitReader {
486+
return &LimitReader{
487+
reader: reader,
488+
bucket: bucket,
489+
}
490+
}
491+
492+
func (lr *LimitReader) Read(p []byte) (int, error) {
493+
sliceLen := int64(len(p))
494+
available := lr.bucket.TakeAvailable(sliceLen)
495+
if available == 0 {
496+
return 0, nil
497+
}
498+
if available == sliceLen {
499+
return lr.reader.Read(p)
500+
}
501+
temp := make([]byte, available)
502+
defer copy(p, temp)
503+
return lr.reader.Read(temp)
504+
}
505+
469506
func sendRequestWithURL(URL *url.URL) http.Handler {
470507
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
471508
ctx := r.Context()
@@ -511,7 +548,11 @@ func sendRequestWithURL(URL *url.URL) http.Handler {
511548
}
512549
}
513550
w.WriteHeader(response.StatusCode)
514-
io.Copy(w, response.Body)
551+
if BandwidthLimiter != nil {
552+
io.Copy(w, NewLimitReader(response.Body, BandwidthLimiter))
553+
} else {
554+
io.Copy(w, response.Body)
555+
}
515556
log.InfoContext(ctx, "Success proxy request: ", URL, " , method: ", request.Method, " , status: ", response.StatusCode)
516557
})
517558
}

0 commit comments

Comments
 (0)