17
17
package backend
18
18
19
19
import (
20
+ "crypto/x509"
21
+ "encoding/json"
20
22
"fmt"
21
23
"net"
22
24
"net/http"
25
+ "net/url"
26
+ "regexp"
27
+ "strconv"
23
28
"strings"
24
29
"time"
25
- )
26
30
27
- import (
28
31
"github.com/baidu/go-lib/log"
29
- )
30
32
31
- import (
32
33
"github.com/bfenetworks/bfe/bfe_config/bfe_cluster_conf/cluster_conf"
33
34
"github.com/bfenetworks/bfe/bfe_debug"
35
+ "github.com/bfenetworks/bfe/bfe_tls"
34
36
)
35
37
38
+ type checkRtn struct {
39
+ ok bool
40
+ err error
41
+ }
42
+
36
43
func UpdateStatus (backend * BfeBackend , cluster string ) bool {
44
+ var (
45
+ checkConf * cluster_conf.BackendCheck
46
+ httpConf * cluster_conf.BackendHTTPS
47
+ )
37
48
// get conf of health check, which is separately stored for each cluster
38
- checkConf : = getCheckConf (cluster )
49
+ checkConf , httpConf = getCheckConf (cluster )
39
50
if checkConf == nil {
40
51
// just ignore if not found health check conf
41
52
return false
@@ -45,14 +56,15 @@ func UpdateStatus(backend *BfeBackend, cluster string) bool {
45
56
// if backend's status become fail, start healthcheck.
46
57
// at most start 1 check goroutine for each backend.
47
58
if backend .UpdateStatus (* checkConf .FailNum ) {
48
- go check (backend , cluster )
59
+ go check (backend , cluster , httpConf )
49
60
return true
50
61
}
51
62
52
63
return false
53
64
}
54
65
55
- func check (backend * BfeBackend , cluster string ) {
66
+ func check (backend * BfeBackend , cluster string , httpConf * cluster_conf.BackendHTTPS ) {
67
+
56
68
log .Logger .Info ("start healthcheck for %s" , backend .Name )
57
69
58
70
// backend close chan
67
79
}
68
80
69
81
// get the latest conf to do health check
70
- checkConf := getCheckConf (cluster )
82
+ checkConf , _ := getCheckConf (cluster )
71
83
if checkConf == nil {
72
84
// never come here
73
85
time .Sleep (time .Second )
76
88
checkInterval := time .Duration (* checkConf .CheckInterval ) * time .Millisecond
77
89
78
90
// health check
79
- if ok , err := CheckConnect (backend , checkConf ); ! ok {
91
+ if ok , err := CheckConnect (backend , checkConf , httpConf ); ! ok {
80
92
backend .ResetSuccNum ()
81
93
if bfe_debug .DebugHealthCheck {
82
94
log .Logger .Debug ("backend %s still not avail (check failure: %s)" , backend .Name , err )
@@ -150,6 +162,232 @@ func doHTTPHealthCheck(request *http.Request, timeout time.Duration) (int, error
150
162
return response .StatusCode , nil
151
163
}
152
164
165
+ // extractIP extract ip address
166
+ func extractIP (rsAddr string ) string {
167
+ if strings .HasPrefix (rsAddr , "[" ) {
168
+ // IPv6
169
+ endIndex := strings .LastIndex (rsAddr , "]" )
170
+ if endIndex == - 1 {
171
+ return ""
172
+ }
173
+ ip := rsAddr [:endIndex + 1 ]
174
+ if net .ParseIP (ip [1 :endIndex ]) == nil {
175
+ return ""
176
+ }
177
+ return ip
178
+ } else {
179
+ // IPv4
180
+ ip := strings .Split (rsAddr , ":" )[0 ]
181
+ if net .ParseIP (ip ) == nil {
182
+ return ""
183
+ }
184
+ return ip
185
+ }
186
+ }
187
+
188
+ func getHostByType (host , rsAddr , hostType * string , def string ) string {
189
+ if hostType == nil {
190
+ ht := cluster_conf .HostType_HOST
191
+ hostType = & ht
192
+ }
193
+ switch * hostType {
194
+ case cluster_conf .HostType_Instance_IP :
195
+ if rsAddr != nil {
196
+ return extractIP (* rsAddr )
197
+ }
198
+ default :
199
+ if host != nil {
200
+ return * host
201
+ }
202
+ }
203
+ return def
204
+ }
205
+
206
+ // add by liangc
207
+ func checkHTTPSConnect (backend * BfeBackend , checkConf * cluster_conf.BackendCheck , httpConf * cluster_conf.BackendHTTPS ) (bool , error ) {
208
+ var (
209
+ err error
210
+ conn net.Conn
211
+ addrInfo = getHealthCheckAddrInfo (backend , checkConf )
212
+ checkTimeout = 30 * time .Second
213
+ statusCode = 0
214
+ host string
215
+ rootCAs * x509.CertPool = nil
216
+ certs []bfe_tls.Certificate = nil
217
+ cert bfe_tls.Certificate
218
+ insecure = false
219
+ uri = "/"
220
+ checkRtnCh = make (chan checkRtn , 1 )
221
+ rtn checkRtn
222
+ )
223
+
224
+ var (
225
+ getStatusCodeFn = func (statusLine string ) (int , error ) {
226
+ // "HTTP/1.1 200 OK"
227
+ re , err := regexp .Compile (`\s(\d{3})\s` )
228
+ if err != nil {
229
+ return 0 , err
230
+ }
231
+ matches := re .FindStringSubmatch (statusLine )
232
+ if len (matches ) == 2 {
233
+ statusCode := matches [1 ]
234
+ log .Logger .Debug ("StatusCode = %s, raw = %s" , statusCode , statusLine )
235
+ return strconv .Atoi (statusCode )
236
+ } else {
237
+ return 0 , fmt .Errorf ("Status code not found: %s" , statusLine )
238
+ }
239
+ }
240
+
241
+ doCheckFn = func (conn net.Conn ) checkRtn {
242
+ // TLS Check >>>>>>>
243
+ if err = conn .(* bfe_tls.Conn ).Handshake (); err != nil {
244
+ log .Logger .Debug ("debug_http err=%s" , err .Error ())
245
+ return checkRtn {false , err }
246
+ }
247
+ if * checkConf .Schem == "tls" { // http or tls
248
+ return checkRtn {true , nil }
249
+ }
250
+ // TLS Check <<<<<<<
251
+
252
+ // HTTPS Check vvvvvvvvvvvvv
253
+ if checkConf .Uri != nil && * checkConf .Uri != "" {
254
+ uri = * checkConf .Uri
255
+ }
256
+ request := fmt .Sprintf ("GET %s HTTP/1.1\r \n " +
257
+ "Host: %s\r \n " +
258
+ "User-Agent: BFE-Health-Check\r \n " +
259
+ "\r \n " , uri , host )
260
+ _ , err = conn .Write ([]byte (request ))
261
+ if err != nil {
262
+ log .Logger .Debug ("debug_http err=%s" , err .Error ())
263
+ return checkRtn {false , err }
264
+ }
265
+ var (
266
+ response = ""
267
+ ok bool
268
+ err error
269
+ data = make ([]byte , 0 )
270
+ bufSz = 128
271
+ buf = make ([]byte , bufSz )
272
+ total = 0
273
+ )
274
+ //TODO: if timeout , how to handle ?
275
+ for {
276
+ total , err = conn .Read (buf )
277
+ if err != nil {
278
+ break
279
+ }
280
+ data = append (data , buf [:total ]... )
281
+ if total < bufSz {
282
+ break
283
+ }
284
+ }
285
+ //data, err = ioutil.ReadAll(conn)
286
+ if err != nil {
287
+ log .Logger .Debug ("debug_http err=%s" , err .Error ())
288
+ return checkRtn {false , err }
289
+ }
290
+ response = string (data )
291
+ log .Logger .Debug ("<- Request:\n %s" , request )
292
+ log .Logger .Debug ("-> Response:\n %s" , response )
293
+ if checkConf .StatusCode != nil { // check status code
294
+ var (
295
+ s string
296
+ arr = strings .Split (response , "\n " )
297
+ )
298
+ if len (arr ) > 0 {
299
+ s = strings .ToUpper (arr [0 ])
300
+ statusCode , err = getStatusCodeFn (s )
301
+ if err != nil {
302
+ return checkRtn {false , err }
303
+ }
304
+ if checkConf .StatusCodeRange != nil && * checkConf .StatusCodeRange != "" {
305
+ log .Logger .Debug ("statusCode=%d, statusCodeRange=%s" , statusCode , * checkConf .StatusCodeRange )
306
+ ok , err := cluster_conf .MatchStatusCodeRange (fmt .Sprintf ("%d" , statusCode ), * checkConf .StatusCodeRange )
307
+ return checkRtn {ok , err }
308
+ }
309
+ }
310
+ ok , err = cluster_conf .MatchStatusCode (statusCode , * checkConf .StatusCode )
311
+ }
312
+ return checkRtn {ok , err }
313
+ }
314
+
315
+ toStringFn = func (o interface {}) string {
316
+ b , err := json .Marshal (o )
317
+ if err != nil {
318
+ return err .Error ()
319
+ }
320
+ return string (b )
321
+ }
322
+ )
323
+
324
+ if checkConf .CheckTimeout != nil {
325
+ checkTimeout = time .Duration (* checkConf .CheckTimeout ) * time .Millisecond
326
+ }
327
+ conn , err = net .DialTimeout ("tcp" , addrInfo , checkTimeout )
328
+
329
+ if err != nil {
330
+ log .Logger .Debug ("debug_http err=%v" , err )
331
+ return false , err
332
+ }
333
+
334
+ defer func () {
335
+ if r := recover (); r != nil {
336
+ log .Logger .Debug ("recover_panic = %v" , r )
337
+ }
338
+ _ = conn .Close ()
339
+ }()
340
+
341
+ _ , err = url .Parse (fmt .Sprintf ("%s://%s%s" , "http" , addrInfo , * checkConf .Uri ))
342
+ if err != nil {
343
+ log .Logger .Debug ("debug_http err=%v" , err )
344
+ return false , err
345
+ }
346
+
347
+ serverName := ""
348
+ if httpConf .RSHost != nil {
349
+ serverName = * httpConf .RSHost
350
+ } else if checkConf .Host != nil {
351
+ serverName = * checkConf .Host
352
+ }
353
+ host = getHostByType (checkConf .Host , & addrInfo , checkConf .HostType , serverName )
354
+
355
+ rootCAs , err = httpConf .GetRSCAList ()
356
+
357
+ if cert , err = httpConf .GetBFECert (); err == nil {
358
+ certs = []bfe_tls.Certificate {cert }
359
+ }
360
+
361
+ if httpConf .RSInsecureSkipVerify != nil {
362
+ insecure = * httpConf .RSInsecureSkipVerify
363
+ }
364
+
365
+ conn = bfe_tls .Client (conn , & bfe_tls.Config {
366
+ Certificates : certs ,
367
+ InsecureSkipVerify : true ,
368
+ ServerName : host ,
369
+ RootCAs : rootCAs ,
370
+ VerifyPeerCertificate : bfe_tls .NewVerifyPeerCertHooks (insecure , host , rootCAs ).Ready (),
371
+ })
372
+
373
+ log .Logger .Debug ("httpCheck conf=%s" , toStringFn (checkConf ))
374
+ go func (conn net.Conn , rtnCh chan checkRtn ) {
375
+ rtnCh <- doCheckFn (conn )
376
+ }(conn , checkRtnCh )
377
+
378
+ if checkTimeout > 0 {
379
+ select {
380
+ case rtn = <- checkRtnCh :
381
+ return rtn .ok , rtn .err
382
+ case <- time .Tick (checkTimeout ):
383
+ return false , fmt .Errorf ("http checkTimeout %dms" , checkTimeout / time .Millisecond )
384
+ }
385
+ } else {
386
+ rtn = <- checkRtnCh
387
+ }
388
+ return rtn .ok , rtn .err
389
+ }
390
+
153
391
func checkHTTPConnect (backend * BfeBackend , checkConf * cluster_conf.BackendCheck ) (bool , error ) {
154
392
// prepare health check request
155
393
addrInfo := getHealthCheckAddrInfo (backend , checkConf )
@@ -182,26 +420,28 @@ func checkHTTPConnect(backend *BfeBackend, checkConf *cluster_conf.BackendCheck)
182
420
}
183
421
184
422
// CheckConnect checks whether backend server become available.
185
- func CheckConnect (backend * BfeBackend , checkConf * cluster_conf.BackendCheck ) (bool , error ) {
423
+ func CheckConnect (backend * BfeBackend , checkConf * cluster_conf.BackendCheck , httpConf * cluster_conf. BackendHTTPS ) (bool , error ) {
186
424
switch * checkConf .Schem {
187
425
case "http" :
188
426
return checkHTTPConnect (backend , checkConf )
189
427
case "tcp" :
190
428
return checkTCPConnect (backend , checkConf )
429
+ case "http" , "tls" :
430
+ return checkHTTPSConnect (backend , checkConf , httpConf )
191
431
default :
192
432
// never come here
193
433
return checkHTTPConnect (backend , checkConf )
194
434
}
195
435
}
196
436
197
437
// CheckConfFetcher returns current health check conf for cluster.
198
- type CheckConfFetcher func (cluster string ) * cluster_conf.BackendCheck
438
+ type CheckConfFetcher func (name string ) ( * cluster_conf.BackendCheck , * cluster_conf. BackendHTTPS )
199
439
200
440
var checkConfFetcher CheckConfFetcher
201
441
202
- func getCheckConf (cluster string ) * cluster_conf.BackendCheck {
442
+ func getCheckConf (cluster string ) ( * cluster_conf.BackendCheck , * cluster_conf. BackendHTTPS ) {
203
443
if checkConfFetcher == nil {
204
- return nil
444
+ return nil , nil
205
445
}
206
446
return checkConfFetcher (cluster )
207
447
}
0 commit comments