#426 Adding support for uuid in url.

Merged Artur Souza artursouza
Showing 2 of 5 files from the diff.
Other files ignored by Codecov
go.mod has changed.
go.sum has changed.

@@ -23,6 +23,7 @@
Loading
23 23
	"net/http"
24 24
	"strconv"
25 25
	"strings"
26 +
	"sync"
26 27
	"time"
27 28
	"unicode/utf8"
28 29
@@ -456,3 +457,23 @@
Loading
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,6 +23,7 @@
Loading
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,6 +35,7 @@
Loading
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,6 +48,10 @@
Loading
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,6 +61,8 @@
Loading
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,10 +303,16 @@
Loading
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,6 +342,30 @@
Loading
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,9 +446,16 @@
Loading
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,6 +511,7 @@
Loading
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,10 +537,26 @@
Loading
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,6 +624,12 @@
Loading
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,7 +678,13 @@
Loading
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,3 +915,8 @@
Loading
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.

Changes in fhttp/http_client.go
-7
+7
Loading file...
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%
Loading