-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathsession.go
322 lines (286 loc) · 8.69 KB
/
session.go
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
package direwolf
import (
"net"
"net/http"
"net/http/cookiejar"
"net/url"
"sync"
"time"
"golang.org/x/net/http/httpproxy"
"golang.org/x/net/publicsuffix"
)
// Session is the main object in direwolf. This is its main features:
// 1. handling redirects
// 2. automatically managing cookies
type Session struct {
client *http.Client
transport *http.Transport
Headers http.Header
Proxy *Proxy
Timeout int
}
// NewSession new a Session object, and set a default Client and Transport.
func NewSession(options ...*SessionOptions) *Session {
var sessionOptions *SessionOptions
if len(options) > 0 {
sessionOptions = options[0]
} else {
sessionOptions = DefaultSessionOptions()
}
// set transport parameters.
trans := &http.Transport{
DialContext: (&net.Dialer{
Timeout: sessionOptions.DialTimeout,
KeepAlive: sessionOptions.DialKeepAlive,
}).DialContext,
MaxIdleConns: sessionOptions.MaxIdleConns,
MaxIdleConnsPerHost: sessionOptions.MaxIdleConnsPerHost,
MaxConnsPerHost: sessionOptions.MaxConnsPerHost,
IdleConnTimeout: sessionOptions.IdleConnTimeout,
TLSHandshakeTimeout: sessionOptions.TLSHandshakeTimeout,
ExpectContinueTimeout: sessionOptions.ExpectContinueTimeout,
Proxy: proxyFunc,
}
if sessionOptions.DisableDialKeepAlives {
trans.DisableKeepAlives = true
}
client := &http.Client{
Transport: trans,
CheckRedirect: redirectFunc,
}
// set CookieJar
if sessionOptions.DisableCookieJar == false {
cookieJarOptions := cookiejar.Options{
PublicSuffixList: publicsuffix.List,
}
jar, err := cookiejar.New(&cookieJarOptions)
if err != nil {
return nil
}
client.Jar = jar
}
// Set default user agent
headers := http.Header{}
headers.Add("User-Agent", "direwolf - winter is coming")
return &Session{
client: client,
transport: trans,
Headers: headers,
}
}
// Send is a generic request method.
func (session *Session) Send(req *Request) (*Response, error) {
resp, err := send(session, req)
if err != nil {
return nil, WrapErr(err, "session send failed")
}
return resp, nil
}
// Get is a get method.
func (session *Session) Get(URL string, args ...RequestOption) (*Response, error) {
req, err := NewRequest("GET", URL, args...)
if err != nil {
return nil, err
}
resp, err := session.Send(req)
if err != nil {
return nil, err
}
return resp, nil
}
// Post is a post method.
func (session *Session) Post(URL string, args ...RequestOption) (*Response, error) {
req, err := NewRequest("POST", URL, args...)
if err != nil {
return nil, err
}
resp, err := session.Send(req)
if err != nil {
return nil, err
}
return resp, nil
}
// Head is a post method.
func (session *Session) Head(URL string, args ...RequestOption) (*Response, error) {
req, err := NewRequest("HEAD", URL, args...)
if err != nil {
return nil, err
}
resp, err := session.Send(req)
if err != nil {
return nil, err
}
return resp, nil
}
// Put is a post method.
func (session *Session) Put(URL string, args ...RequestOption) (*Response, error) {
req, err := NewRequest("PUT", URL, args...)
if err != nil {
return nil, err
}
resp, err := session.Send(req)
if err != nil {
return nil, err
}
return resp, nil
}
// Patch is a post method.
func (session *Session) Patch(URL string, args ...RequestOption) (*Response, error) {
req, err := NewRequest("PATCH", URL, args...)
if err != nil {
return nil, err
}
resp, err := session.Send(req)
if err != nil {
return nil, err
}
return resp, nil
}
// Delete is a post method.
func (session *Session) Delete(URL string, args ...RequestOption) (*Response, error) {
req, err := NewRequest("DELETE", URL, args...)
if err != nil {
return nil, err
}
resp, err := session.Send(req)
if err != nil {
return nil, err
}
return resp, nil
}
// Cookies returns the cookies of the given url in Session.
func (session *Session) Cookies(URL string) Cookies {
if session.client.Jar == nil {
return nil
}
parsedURL, err := url.Parse(URL)
if err != nil {
return nil
}
return session.client.Jar.Cookies(parsedURL)
}
// SetCookies set cookies of the url in Session.
func (session *Session) SetCookies(URL string, cookies Cookies) {
if session.client.Jar == nil {
return
}
parsedURL, err := url.Parse(URL)
if err != nil {
return
}
session.client.Jar.SetCookies(parsedURL, cookies)
}
type SessionOptions struct {
// DialTimeout is the maximum amount of time a dial will wait for
// a connect to complete.
//
// When using TCP and dialing a host name with multiple IP
// addresses, the timeout may be divided between them.
//
// With or without a timeout, the operating system may impose
// its own earlier timeout. For instance, TCP timeouts are
// often around 3 minutes.
DialTimeout time.Duration
// DialKeepAlive specifies the interval between keep-alive
// probes for an active network connection.
//
// Network protocols or operating systems that do
// not support keep-alives ignore this field.
// If negative, keep-alive probes are disabled.
DialKeepAlive time.Duration
// MaxConnsPerHost optionally limits the total number of
// connections per host, including connections in the dialing,
// active, and idle states. On limit violation, dials will block.
//
// Zero means no limit.
MaxConnsPerHost int
// MaxIdleConns controls the maximum number of idle (keep-alive)
// connections across all hosts. Zero means no limit.
MaxIdleConns int
// MaxIdleConnsPerHost, if non-zero, controls the maximum idle
// (keep-alive) connections to keep per-host. If zero,
// DefaultMaxIdleConnsPerHost is used.
MaxIdleConnsPerHost int
// IdleConnTimeout is the maximum amount of time an idle
// (keep-alive) connection will remain idle before closing
// itself.
// Zero means no limit.
IdleConnTimeout time.Duration
// TLSHandshakeTimeout specifies the maximum amount of time waiting to
// wait for a TLS handshake. Zero means no timeout.
TLSHandshakeTimeout time.Duration
// ExpectContinueTimeout, if non-zero, specifies the amount of
// time to wait for a server's first response headers after fully
// writing the request headers if the request has an
// "Expect: 100-continue" header. Zero means no timeout and
// causes the body to be sent immediately, without
// waiting for the server to approve.
// This time does not include the time to send the request header.
ExpectContinueTimeout time.Duration
// DisableCookieJar specifies whether disable session cookiejar.
DisableCookieJar bool
// DisableDialKeepAlives, if true, disables HTTP keep-alives and
// will only use the connection to the server for a single
// HTTP request.
//
// This is unrelated to the similarly named TCP keep-alives.
DisableDialKeepAlives bool
}
// DefaultSessionOptions return a default SessionOptions object.
func DefaultSessionOptions() *SessionOptions {
return &SessionOptions{
DialTimeout: 30 * time.Second,
DialKeepAlive: 30 * time.Second,
MaxConnsPerHost: 0,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 2,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
DisableCookieJar: false,
DisableDialKeepAlives: false,
}
}
var (
// proxyConfigOnce guards proxyConfig
envProxyOnce sync.Once
envProxyFuncValue func(*url.URL) (*url.URL, error)
)
// proxyFunc get proxy from request context.
// If there is no proxy set, use default proxy from environment.
func proxyFunc(req *http.Request) (*url.URL, error) {
httpURLStr := req.Context().Value("http") // get http proxy url form context
httpsURLStr := req.Context().Value("https") // get https proxy url form context
// If there is no proxy set, use default proxy from environment.
// This mitigates expensive lookups on some platforms (e.g. Windows).
envProxyOnce.Do(func() {
envProxyFuncValue = httpproxy.FromEnvironment().ProxyFunc()
})
if req.URL.Scheme == "http" { // set proxy for http site
if httpURLStr != nil {
httpURL, err := url.Parse(httpURLStr.(string))
if err != nil {
return nil, WrapErr(err, "HTTP Proxy error, please check proxy url")
}
return httpURL, nil
}
} else if req.URL.Scheme == "https" { // set proxy for https site
if httpsURLStr != nil {
httpsURL, err := url.Parse(httpsURLStr.(string))
if err != nil {
return nil, WrapErr(err, "HTTPS Proxy error, please check proxy url")
}
return httpsURL, nil
}
}
return envProxyFuncValue(req.URL)
}
// redirectFunc get redirectNum from request context and check redirect number.
func redirectFunc(req *http.Request, via []*http.Request) error {
redirectNum := req.Context().Value("redirectNum").(int)
if len(via) > redirectNum {
err := &RedirectError{redirectNum}
return WrapErr(err, "RedirectError")
}
return nil
}