1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
// Copyright (C) 2019-2023 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.
package network
import (
"errors"
"net/http"
"time"
"github.com/algorand/go-algorand/util"
)
// rateLimitingTransport is the transport for execute a single HTTP transaction, obtaining the Response for a given Request.
type rateLimitingTransport struct {
phonebook Phonebook
innerTransport *http.Transport
queueingTimeout time.Duration
}
// ErrConnectionQueueingTimeout indicates that we've exceeded the time allocated for
// queueing the current request before the request attempt could be made.
var ErrConnectionQueueingTimeout = errors.New("rateLimitingTransport: queueing timeout")
// makeRateLimitingTransport creates a rate limiting http transport that would limit the requests rate
// according to the entries in the phonebook.
func makeRateLimitingTransport(phonebook Phonebook, queueingTimeout time.Duration, dialer *Dialer, maxIdleConnsPerHost int) rateLimitingTransport {
defaultTransport := http.DefaultTransport.(*http.Transport)
return rateLimitingTransport{
phonebook: phonebook,
innerTransport: &http.Transport{
Proxy: defaultTransport.Proxy,
DialContext: dialer.innerDialContext,
MaxIdleConns: defaultTransport.MaxIdleConns,
IdleConnTimeout: defaultTransport.IdleConnTimeout,
TLSHandshakeTimeout: defaultTransport.TLSHandshakeTimeout,
ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
MaxIdleConnsPerHost: maxIdleConnsPerHost,
},
queueingTimeout: queueingTimeout,
}
}
// RoundTrip connects to the address on the named network using the provided context.
// It waits if needed not to exceed connectionsRateLimitingCount.
func (r *rateLimitingTransport) RoundTrip(req *http.Request) (res *http.Response, err error) {
var waitTime time.Duration
var provisionalTime time.Time
queueingDeadline := time.Now().Add(r.queueingTimeout)
for {
_, waitTime, provisionalTime = r.phonebook.GetConnectionWaitTime(req.Host)
if waitTime == 0 {
break // break out of the loop and proceed to the connection
}
waitDeadline := time.Now().Add(waitTime)
if waitDeadline.Before(queueingDeadline) {
util.NanoSleep(waitTime)
continue
}
return nil, ErrConnectionQueueingTimeout
}
res, err = r.innerTransport.RoundTrip(req)
r.phonebook.UpdateConnectionTime(req.Host, provisionalTime)
return
}
|