No flags found
Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.
e.g., #unittest #integration
#production #enterprise
#frontend #backend
Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.
e.g., #unittest #integration
#production #enterprise
#frontend #backend
23 | 23 | "net/http" |
|
24 | 24 | "strconv" |
|
25 | 25 | "strings" |
|
26 | + | "sync" |
|
26 | 27 | "time" |
|
27 | 28 | "unicode/utf8" |
|
28 | 29 |
456 | 457 | func generateBase64UserCredentials(userCredentials string) string { |
|
457 | 458 | return "Basic " + base64.StdEncoding.EncodeToString([]byte(userCredentials)) |
|
458 | 459 | } |
|
460 | + | ||
461 | + | // SyncReader is a thread-safe wrapper for a reader. |
|
462 | + | type SyncReader struct { |
|
463 | + | lk sync.Mutex |
|
464 | + | reader io.Reader |
|
465 | + | } |
|
466 | + | ||
467 | + | // NewSyncReader returns a new thread-safe reader. |
|
468 | + | func NewSyncReader(reader io.Reader) *SyncReader { |
|
469 | + | return &SyncReader{ |
|
470 | + | reader: reader, |
|
471 | + | } |
|
472 | + | } |
|
473 | + | ||
474 | + | func (r *SyncReader) Read(p []byte) (n int, err error) { |
|
475 | + | r.lk.Lock() |
|
476 | + | defer r.lk.Unlock() |
|
477 | + | ||
478 | + | return r.reader.Read(p) |
|
479 | + | } |
23 | 23 | "fmt" |
|
24 | 24 | "io" |
|
25 | 25 | "io/ioutil" |
|
26 | + | "math/rand" |
|
26 | 27 | "net" |
|
27 | 28 | "net/http" |
|
28 | 29 | "net/http/httputil" |
34 | 35 | "fortio.org/fortio/fnet" |
|
35 | 36 | "fortio.org/fortio/log" |
|
36 | 37 | "fortio.org/fortio/version" |
|
38 | + | "github.com/google/uuid" |
|
37 | 39 | ) |
|
38 | 40 | ||
39 | 41 | // Fetcher is the Url content fetcher that the different client implements. |
46 | 48 | Close() int |
|
47 | 49 | } |
|
48 | 50 | ||
51 | + | const ( |
|
52 | + | uuidToken = "{uuid}" |
|
53 | + | ) |
|
54 | + | ||
49 | 55 | var ( |
|
50 | 56 | // BufferSizeKb size of the buffer (max data) for optimized client in kilobytes defaults to 128k. |
|
51 | 57 | BufferSizeKb = 128 |
55 | 61 | contentLengthHeader = []byte("\r\ncontent-length:") |
|
56 | 62 | connectionCloseHeader = []byte("\r\nconnection: close") |
|
57 | 63 | chunkedHeader = []byte("\r\nTransfer-Encoding: chunked") |
|
64 | + | //nolint // G404 is expected because we are using weak random number generator due to performance. |
|
65 | + | rander = NewSyncReader(rand.New(rand.NewSource(time.Now().UnixNano()))) |
|
58 | 66 | ) |
|
59 | 67 | ||
60 | 68 | // NewHTTPOptions creates and initialize a HTTPOptions object. |
295 | 303 | // Client object for making repeated requests of the same URL using the same |
|
296 | 304 | // http client (net/http). |
|
297 | 305 | type Client struct { |
|
298 | - | url string |
|
299 | - | req *http.Request |
|
300 | - | client *http.Client |
|
301 | - | transport *http.Transport |
|
306 | + | url string |
|
307 | + | path string // original path of the request's url |
|
308 | + | rawQuery string // original query params |
|
309 | + | body string // original body of the request |
|
310 | + | req *http.Request |
|
311 | + | client *http.Client |
|
312 | + | transport *http.Transport |
|
313 | + | pathContainsUUID bool // if url contains the "{uuid}" pattern (lowercase) |
|
314 | + | rawQueryContainsUUID bool // if any query params contains the "{uuid}" pattern (lowercase) |
|
315 | + | bodyContainsUUID bool // if body contains the "{uuid}" pattern (lowercase) |
|
302 | 316 | } |
|
303 | 317 | ||
304 | 318 | // Close cleans up any resources used by NewStdClient. |
328 | 342 | // Fetch fetches the byte and code for pre created client. |
|
329 | 343 | func (c *Client) Fetch() (int, []byte, int) { |
|
330 | 344 | // req can't be null (client itself would be null in that case) |
|
345 | + | if c.pathContainsUUID { |
|
346 | + | path := c.path |
|
347 | + | for strings.Contains(path, uuidToken) { |
|
348 | + | path = strings.Replace(path, uuidToken, generateUUID(), 1) |
|
349 | + | } |
|
350 | + | c.req.URL.Path = path |
|
351 | + | } |
|
352 | + | if c.rawQueryContainsUUID { |
|
353 | + | rawQuery := c.rawQuery |
|
354 | + | for strings.Contains(rawQuery, uuidToken) { |
|
355 | + | rawQuery = strings.Replace(rawQuery, uuidToken, generateUUID(), 1) |
|
356 | + | } |
|
357 | + | ||
358 | + | c.req.URL.RawQuery = rawQuery |
|
359 | + | } |
|
360 | + | if c.bodyContainsUUID { |
|
361 | + | body := c.body |
|
362 | + | for strings.Contains(body, uuidToken) { |
|
363 | + | body = strings.Replace(body, uuidToken, generateUUID(), 1) |
|
364 | + | } |
|
365 | + | bodyBytes := []byte(body) |
|
366 | + | c.req.ContentLength = int64(len(bodyBytes)) |
|
367 | + | c.req.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes)) |
|
368 | + | } |
|
331 | 369 | resp, err := c.client.Do(c.req) |
|
332 | 370 | if err != nil { |
|
333 | 371 | log.Errf("Unable to send %s request for %s : %v", c.req.Method, c.url, err) |
408 | 446 | } |
|
409 | 447 | } |
|
410 | 448 | } |
|
449 | + | ||
411 | 450 | client := Client{ |
|
412 | - | url: o.URL, |
|
413 | - | req: req, |
|
451 | + | url: o.URL, |
|
452 | + | path: req.URL.Path, |
|
453 | + | pathContainsUUID: strings.Contains(req.URL.Path, uuidToken), |
|
454 | + | rawQuery: req.URL.RawQuery, |
|
455 | + | rawQueryContainsUUID: strings.Contains(req.URL.RawQuery, uuidToken), |
|
456 | + | body: o.PayloadString(), |
|
457 | + | bodyContainsUUID: strings.Contains(o.PayloadString(), uuidToken), |
|
458 | + | req: req, |
|
414 | 459 | client: &http.Client{ |
|
415 | 460 | Timeout: o.HTTPReqTimeOut, |
|
416 | 461 | Transport: &tr, |
466 | 511 | parseHeaders bool // don't bother in http/1.0 |
|
467 | 512 | halfClose bool // allow/do half close when keepAlive is false |
|
468 | 513 | reqTimeout time.Duration |
|
514 | + | uuidMarkers [][]byte |
|
469 | 515 | } |
|
470 | 516 | ||
471 | 517 | // Close cleans up any resources used by FastClient. |
491 | 537 | if o.HTTP10 { |
|
492 | 538 | proto = "1.0" |
|
493 | 539 | } |
|
540 | + | ||
541 | + | uuidStrings := []string{} |
|
542 | + | urlString := o.URL |
|
543 | + | for strings.Contains(urlString, uuidToken) { |
|
544 | + | uuidString := generateUUID() |
|
545 | + | uuidStrings = append(uuidStrings, uuidString) |
|
546 | + | urlString = strings.Replace(urlString, uuidToken, uuidString, 1) |
|
547 | + | } |
|
548 | + | payload := o.PayloadString() |
|
549 | + | for strings.Contains(payload, uuidToken) { |
|
550 | + | uuidString := generateUUID() |
|
551 | + | uuidStrings = append(uuidStrings, uuidString) |
|
552 | + | payload = strings.Replace(payload, uuidToken, uuidString, 1) |
|
553 | + | } |
|
554 | + | o.Payload = []byte(payload) |
|
555 | + | ||
494 | 556 | // Parse the url, extract components. |
|
495 | - | url, err := url.Parse(o.URL) |
|
557 | + | url, err := url.Parse(urlString) |
|
496 | 558 | if err != nil { |
|
497 | - | log.Errf("Bad url '%s' : %v", o.URL, err) |
|
559 | + | log.Errf("Bad url '%s' : %v", urlString, err) |
|
498 | 560 | return nil, err |
|
499 | 561 | } |
|
500 | 562 | if url.Scheme != "http" { |
562 | 624 | buf.Write(o.Payload) |
|
563 | 625 | } |
|
564 | 626 | bc.req = buf.Bytes() |
|
627 | + | bc.uuidMarkers = [][]byte{} |
|
628 | + | if len(uuidStrings) > 0 { |
|
629 | + | for _, uuidString := range uuidStrings { |
|
630 | + | bc.uuidMarkers = append(bc.uuidMarkers, []byte(uuidString)) |
|
631 | + | } |
|
632 | + | } |
|
565 | 633 | log.Debugf("Created client:\n%+v\n%s", bc.dest, bc.req) |
|
566 | 634 | return &bc, nil |
|
567 | 635 | } |
610 | 678 | c.socket = nil // because of error returns and single retry |
|
611 | 679 | conErr := conn.SetReadDeadline(time.Now().Add(c.reqTimeout)) |
|
612 | 680 | // Send the request: |
|
613 | - | n, err := conn.Write(c.req) |
|
681 | + | req := c.req |
|
682 | + | if len(c.uuidMarkers) > 0 { |
|
683 | + | for _, uuidMarker := range c.uuidMarkers { |
|
684 | + | req = bytes.Replace(req, uuidMarker, []byte(generateUUID()), 1) |
|
685 | + | } |
|
686 | + | } |
|
687 | + | n, err := conn.Write(req) |
|
614 | 688 | if err != nil || conErr != nil { |
|
615 | 689 | if reuse { |
|
616 | 690 | // it's ok for the (idle) socket to die once, auto reconnect: |
841 | 915 | // we cleared c.socket in caller already |
|
842 | 916 | } |
|
843 | 917 | } |
|
918 | + | ||
919 | + | func generateUUID() string { |
|
920 | + | // We use math random instead of crypto random generator due to performance. |
|
921 | + | return uuid.Must(uuid.NewRandomFromReader(rander)).String() |
|
922 | + | } |
Learn more Showing 1 files with coverage changes found.
fhttp/http_client.go
Files | Coverage |
---|---|
dflag | 82.2% |
fgrpc | 87.9% |
fhttp | +1.2% 85.4% |
fnet/network.go | 86.9% |
log/logger.go | 86.7% |
periodic/periodic.go | 96.9% |
stats/stats.go | 96.5% |
tcprunner/tcprunner.go | 70.2% |
Project Totals (25 files) | 86.7% |
#426
35440c5
#426
4bdd947
7860ce8
e998da1
8ecc186
12ad829
696d6ac