Skip to content

Commit 122f1d5

Browse files
committed
fix httplib
1 parent db70766 commit 122f1d5

File tree

3 files changed

+88
-146
lines changed

3 files changed

+88
-146
lines changed

modules/httplib/request.go

Lines changed: 53 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -7,54 +7,59 @@ package httplib
77
import (
88
"bytes"
99
"context"
10-
"crypto/tls"
11-
"errors"
1210
"fmt"
1311
"io"
1412
"net"
1513
"net/http"
1614
"net/url"
1715
"strings"
16+
"sync"
1817
"time"
1918
)
2019

21-
var defaultSetting = Settings{"GiteaServer", 60 * time.Second, 60 * time.Second, nil, nil}
22-
23-
// newRequest returns *Request with specific method
24-
func newRequest(url, method string) *Request {
25-
var resp http.Response
26-
req := http.Request{
27-
Method: method,
28-
Header: make(http.Header),
29-
Proto: "HTTP/1.1",
30-
ProtoMajor: 1,
31-
ProtoMinor: 1,
20+
var defaultTransport = sync.OnceValue(func() http.RoundTripper {
21+
return &http.Transport{
22+
Proxy: http.ProxyFromEnvironment,
23+
DialContext: DialContextWithTimeout(10 * time.Second), // it is good enough in modern days
3224
}
33-
return &Request{url, &req, map[string]string{}, defaultSetting, &resp, nil}
34-
}
25+
})
3526

36-
// NewRequest returns *Request with specific method
37-
func NewRequest(url, method string) *Request {
38-
return newRequest(url, method)
27+
func DialContextWithTimeout(timeout time.Duration) func(ctx context.Context, netw, addr string) (net.Conn, error) {
28+
return func(ctx context.Context, netw, addr string) (net.Conn, error) {
29+
d := net.Dialer{Timeout: timeout}
30+
conn, err := d.DialContext(ctx, netw, addr)
31+
if err != nil {
32+
return nil, err
33+
}
34+
return conn, nil
35+
}
3936
}
4037

41-
// Settings is the default settings for http client
42-
type Settings struct {
43-
UserAgent string
44-
ConnectTimeout time.Duration
45-
ReadWriteTimeout time.Duration
46-
TLSClientConfig *tls.Config
47-
Transport http.RoundTripper
38+
func NewRequest(url, method string) *Request {
39+
return &Request{
40+
url: url,
41+
req: &http.Request{
42+
Method: method,
43+
Header: make(http.Header),
44+
Proto: "HTTP/1.1", // FIXME: from legacy httplib, it shouldn't be hardcoded
45+
ProtoMajor: 1,
46+
ProtoMinor: 1,
47+
},
48+
params: map[string]string{},
49+
50+
// from legacy httplib, caller's must pay more attention to it, it will cause annoying bugs when the response takes a long time
51+
readWriteTimeout: 60 * time.Second,
52+
}
4853
}
4954

50-
// Request provides more useful methods for requesting one url than http.Request.
5155
type Request struct {
52-
url string
53-
req *http.Request
54-
params map[string]string
55-
setting Settings
56-
resp *http.Response
57-
body []byte
56+
url string
57+
req *http.Request
58+
params map[string]string
59+
body []byte
60+
61+
readWriteTimeout time.Duration
62+
transport http.RoundTripper
5863
}
5964

6065
// SetContext sets the request's Context
@@ -63,36 +68,24 @@ func (r *Request) SetContext(ctx context.Context) *Request {
6368
return r
6469
}
6570

66-
// SetTimeout sets connect time out and read-write time out for BeegoRequest.
67-
func (r *Request) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *Request {
68-
r.setting.ConnectTimeout = connectTimeout
69-
r.setting.ReadWriteTimeout = readWriteTimeout
71+
// SetTransport sets the request transport, if not set, will use httplib's default transport with environment proxy support
72+
// ATTENTION: the http.Transport has connection pool, so you should reuse it as much as possible, do not create a lot of transports
73+
func (r *Request) SetTransport(transport http.RoundTripper) *Request {
74+
r.transport = transport
7075
return r
7176
}
7277

7378
func (r *Request) SetReadWriteTimeout(readWriteTimeout time.Duration) *Request {
74-
r.setting.ReadWriteTimeout = readWriteTimeout
75-
return r
76-
}
77-
78-
// SetTLSClientConfig sets tls connection configurations if visiting https url.
79-
func (r *Request) SetTLSClientConfig(config *tls.Config) *Request {
80-
r.setting.TLSClientConfig = config
79+
r.readWriteTimeout = readWriteTimeout
8180
return r
8281
}
8382

84-
// Header add header item string in request.
83+
// Header set header item string in request.
8584
func (r *Request) Header(key, value string) *Request {
8685
r.req.Header.Set(key, value)
8786
return r
8887
}
8988

90-
// SetTransport sets transport to
91-
func (r *Request) SetTransport(transport http.RoundTripper) *Request {
92-
r.setting.Transport = transport
93-
return r
94-
}
95-
9689
// Param adds query param in to request.
9790
// params build query string as ?key1=value1&key2=value2...
9891
func (r *Request) Param(key, value string) *Request {
@@ -125,11 +118,9 @@ func (r *Request) Body(data any) *Request {
125118
return r
126119
}
127120

128-
func (r *Request) getResponse() (*http.Response, error) {
129-
if r.resp.StatusCode != 0 {
130-
return r.resp, nil
131-
}
132-
121+
// Response executes request client and returns the response.
122+
// Caller MUST close the response body if no error occurs.
123+
func (r *Request) Response() (*http.Response, error) {
133124
var paramBody string
134125
if len(r.params) > 0 {
135126
var buf bytes.Buffer
@@ -160,59 +151,19 @@ func (r *Request) getResponse() (*http.Response, error) {
160151
return nil, err
161152
}
162153

163-
trans := r.setting.Transport
164-
if trans == nil {
165-
// create default transport
166-
trans = &http.Transport{
167-
TLSClientConfig: r.setting.TLSClientConfig,
168-
Proxy: http.ProxyFromEnvironment,
169-
DialContext: TimeoutDialer(r.setting.ConnectTimeout),
170-
}
171-
} else if t, ok := trans.(*http.Transport); ok {
172-
if t.TLSClientConfig == nil {
173-
t.TLSClientConfig = r.setting.TLSClientConfig
174-
}
175-
if t.DialContext == nil {
176-
t.DialContext = TimeoutDialer(r.setting.ConnectTimeout)
177-
}
178-
}
179-
180154
client := &http.Client{
181-
Transport: trans,
182-
Timeout: r.setting.ReadWriteTimeout,
155+
Transport: r.transport,
156+
Timeout: r.readWriteTimeout,
183157
}
184-
185-
if len(r.setting.UserAgent) > 0 && len(r.req.Header.Get("User-Agent")) == 0 {
186-
r.req.Header.Set("User-Agent", r.setting.UserAgent)
187-
}
188-
189-
resp, err := client.Do(r.req)
190-
if err != nil {
191-
return nil, err
158+
if client.Transport == nil {
159+
client.Transport = defaultTransport()
192160
}
193-
r.resp = resp
194-
return resp, nil
195-
}
196161

197-
// Response executes request client gets response manually.
198-
// Caller MUST close the response body if no error occurs
199-
func (r *Request) Response() (*http.Response, error) {
200-
if r == nil {
201-
return nil, errors.New("invalid request")
162+
if r.req.Header.Get("User-Agent") == "" {
163+
r.req.Header.Set("User-Agent", "GiteaHttpLib")
202164
}
203-
return r.getResponse()
204-
}
205165

206-
// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
207-
func TimeoutDialer(cTimeout time.Duration) func(ctx context.Context, net, addr string) (c net.Conn, err error) {
208-
return func(ctx context.Context, netw, addr string) (net.Conn, error) {
209-
d := net.Dialer{Timeout: cTimeout}
210-
conn, err := d.DialContext(ctx, netw, addr)
211-
if err != nil {
212-
return nil, err
213-
}
214-
return conn, nil
215-
}
166+
return client.Do(r.req)
216167
}
217168

218169
func (r *Request) GoString() string {

modules/private/internal.go

Lines changed: 34 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/http"
1111
"os"
1212
"strings"
13+
"sync"
1314
"time"
1415

1516
"code.gitea.io/gitea/modules/httplib"
@@ -33,6 +34,35 @@ func getClientIP() string {
3334
return strings.Fields(sshConnEnv)[0]
3435
}
3536

37+
func dialContextInternalAPI(ctx context.Context, network, address string) (conn net.Conn, err error) {
38+
d := net.Dialer{Timeout: 10 * time.Second}
39+
if setting.Protocol == setting.HTTPUnix {
40+
conn, err = d.DialContext(ctx, "unix", setting.HTTPAddr)
41+
} else {
42+
conn, err = d.DialContext(ctx, network, address)
43+
}
44+
if err != nil {
45+
return conn, err
46+
}
47+
if setting.LocalUseProxyProtocol {
48+
if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
49+
_ = conn.Close()
50+
return nil, err
51+
}
52+
}
53+
return conn, err
54+
}
55+
56+
var internalAPITransport = sync.OnceValue(func() http.RoundTripper {
57+
return &http.Transport{
58+
DialContext: dialContextInternalAPI,
59+
TLSClientConfig: &tls.Config{
60+
InsecureSkipVerify: true,
61+
ServerName: setting.Domain,
62+
},
63+
}
64+
})
65+
3666
func NewInternalRequest(ctx context.Context, url, method string) *httplib.Request {
3767
if setting.InternalToken == "" {
3868
log.Fatal(`The INTERNAL_TOKEN setting is missing from the configuration file: %q.
@@ -43,49 +73,11 @@ Ensure you are running in the correct environment or set the correct configurati
4373
log.Fatal("Invalid internal request URL: %q", url)
4474
}
4575

46-
req := httplib.NewRequest(url, method).
76+
return httplib.NewRequest(url, method).
4777
SetContext(ctx).
78+
SetTransport(internalAPITransport()).
4879
Header("X-Real-IP", getClientIP()).
49-
Header("X-Gitea-Internal-Auth", "Bearer "+setting.InternalToken).
50-
SetTLSClientConfig(&tls.Config{
51-
InsecureSkipVerify: true,
52-
ServerName: setting.Domain,
53-
})
54-
55-
if setting.Protocol == setting.HTTPUnix {
56-
req.SetTransport(&http.Transport{
57-
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
58-
var d net.Dialer
59-
conn, err := d.DialContext(ctx, "unix", setting.HTTPAddr)
60-
if err != nil {
61-
return conn, err
62-
}
63-
if setting.LocalUseProxyProtocol {
64-
if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
65-
_ = conn.Close()
66-
return nil, err
67-
}
68-
}
69-
return conn, err
70-
},
71-
})
72-
} else if setting.LocalUseProxyProtocol {
73-
req.SetTransport(&http.Transport{
74-
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
75-
var d net.Dialer
76-
conn, err := d.DialContext(ctx, network, address)
77-
if err != nil {
78-
return conn, err
79-
}
80-
if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
81-
_ = conn.Close()
82-
return nil, err
83-
}
84-
return conn, err
85-
},
86-
})
87-
}
88-
return req
80+
Header("X-Gitea-Internal-Auth", "Bearer "+setting.InternalToken)
8981
}
9082

9183
func newInternalRequestAPI(ctx context.Context, url, method string, body ...any) *httplib.Request {
@@ -98,6 +90,6 @@ func newInternalRequestAPI(ctx context.Context, url, method string, body ...any)
9890
log.Fatal("Too many arguments for newInternalRequestAPI")
9991
}
10092

101-
req.SetTimeout(10*time.Second, 60*time.Second)
93+
req.SetReadWriteTimeout(60 * time.Second)
10294
return req
10395
}

modules/private/restore_repo.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package private
66
import (
77
"context"
88
"fmt"
9-
"time"
109

1110
"code.gitea.io/gitea/modules/setting"
1211
)
@@ -31,6 +30,6 @@ func RestoreRepo(ctx context.Context, repoDir, ownerName, repoName string, units
3130
Units: units,
3231
Validation: validation,
3332
})
34-
req.SetTimeout(3*time.Second, 0) // since the request will spend much time, don't timeout
33+
req.SetReadWriteTimeout(0) // since the request will spend much time, don't timeout
3534
return requestJSONClientMsg(req, fmt.Sprintf("Restore repo %s/%s successfully", ownerName, repoName))
3635
}

0 commit comments

Comments
 (0)