diff --git a/js/modules/k6/http/http.go b/js/modules/k6/http/http.go
index 9aca5f2d82e..24bc72e9f9b 100644
--- a/js/modules/k6/http/http.go
+++ b/js/modules/k6/http/http.go
@@ -26,6 +26,7 @@ import (
"go.k6.io/k6/js/common"
"go.k6.io/k6/lib"
"go.k6.io/k6/lib/netext"
+ "go.k6.io/k6/lib/netext/httpext"
)
const (
@@ -99,10 +100,12 @@ type HTTP struct {
responseCallback func(int) bool
}
+// XCookieJar creates a new cookie jar object.
func (*HTTP) XCookieJar(ctx *context.Context) *HTTPCookieJar {
return newCookieJar(ctx)
}
+// CookieJar returns the active cookie jar for the current VU.
func (*HTTP) CookieJar(ctx context.Context) (*HTTPCookieJar, error) {
state := lib.GetState(ctx)
if state == nil {
@@ -110,3 +113,17 @@ func (*HTTP) CookieJar(ctx context.Context) (*HTTPCookieJar, error) {
}
return &HTTPCookieJar{state.CookieJar, &ctx}, nil
}
+
+// URL creates a new URL from the provided parts
+func (*HTTP) URL(parts []string, pieces ...string) (httpext.URL, error) {
+ var name, urlstr string
+ for i, part := range parts {
+ name += part
+ urlstr += part
+ if i < len(pieces) {
+ name += "${}"
+ urlstr += pieces[i]
+ }
+ }
+ return httpext.NewURL(urlstr, name)
+}
diff --git a/js/modules/k6/http/http_url.go b/js/modules/k6/http/http_url.go
deleted file mode 100644
index 7189a929c80..00000000000
--- a/js/modules/k6/http/http_url.go
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- *
- * k6 - a next-generation load testing tool
- * Copyright (C) 2016 Load Impact
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- *
- */
-
-package http
-
-import (
- "fmt"
-
- "github.com/dop251/goja"
-
- "go.k6.io/k6/lib/netext/httpext"
-)
-
-// ToURL tries to convert anything passed to it to a k6 URL struct
-func ToURL(u interface{}) (httpext.URL, error) {
- switch tu := u.(type) {
- case httpext.URL:
- // Handling of http.url`http://example.com/{$id}`
- return tu, nil
- case string:
- // Handling of "http://example.com/"
- return httpext.NewURL(tu, tu)
- case goja.Value:
- // Unwrap goja values
- return ToURL(tu.Export())
- default:
- return httpext.URL{}, fmt.Errorf("invalid URL value '%#v'", u)
- }
-}
-
-// URL creates new URL from the provided parts
-func (http *HTTP) URL(parts []string, pieces ...string) (httpext.URL, error) {
- var name, urlstr string
- for i, part := range parts {
- name += part
- urlstr += part
- if i < len(pieces) {
- name += "${}"
- urlstr += pieces[i]
- }
- }
- return httpext.NewURL(urlstr, name)
-}
diff --git a/js/modules/k6/http/request.go b/js/modules/k6/http/request.go
index a19b0611980..44644022081 100644
--- a/js/modules/k6/http/request.go
+++ b/js/modules/k6/http/request.go
@@ -23,6 +23,7 @@ package http
import (
"bytes"
"context"
+ "errors"
"fmt"
"mime/multipart"
"net/http"
@@ -89,9 +90,9 @@ func (h *HTTP) Options(ctx context.Context, url goja.Value, args ...goja.Value)
// Request makes an http request of the provided `method` and returns a corresponding response by
// taking goja.Values as arguments
func (h *HTTP) Request(ctx context.Context, method string, url goja.Value, args ...goja.Value) (*Response, error) {
- u, err := ToURL(url)
- if err != nil {
- return nil, err
+ state := lib.GetState(ctx)
+ if state == nil {
+ return nil, ErrHTTPForbiddenInInitContext
}
var body interface{}
@@ -104,9 +105,19 @@ func (h *HTTP) Request(ctx context.Context, method string, url goja.Value, args
params = args[1]
}
- req, err := h.parseRequest(ctx, method, u, body, params)
+ req, err := h.parseRequest(ctx, method, url, body, params)
if err != nil {
- return nil, err
+ if state.Options.Throw.Bool {
+ return nil, err
+ }
+ state.Logger.WithField("error", err).Warn("Request Failed")
+ r := httpext.NewResponse(ctx)
+ r.Error = err.Error()
+ var k6e httpext.K6Error
+ if errors.As(err, &k6e) {
+ r.ErrorCode = int(k6e.Code)
+ }
+ return &Response{Response: r}, nil
}
resp, err := httpext.MakeRequest(ctx, req)
@@ -120,7 +131,7 @@ func (h *HTTP) Request(ctx context.Context, method string, url goja.Value, args
//TODO break this function up
//nolint: gocyclo
func (h *HTTP) parseRequest(
- ctx context.Context, method string, reqURL httpext.URL, body interface{}, params goja.Value,
+ ctx context.Context, method string, reqURL, body interface{}, params goja.Value,
) (*httpext.ParsedHTTPRequest, error) {
rt := common.GetRuntime(ctx)
state := lib.GetState(ctx)
@@ -128,11 +139,19 @@ func (h *HTTP) parseRequest(
return nil, ErrHTTPForbiddenInInitContext
}
+ if urlJSValue, ok := reqURL.(goja.Value); ok {
+ reqURL = urlJSValue.Export()
+ }
+ u, err := httpext.ToURL(reqURL)
+ if err != nil {
+ return nil, err
+ }
+
result := &httpext.ParsedHTTPRequest{
- URL: &reqURL,
+ URL: &u,
Req: &http.Request{
Method: method,
- URL: reqURL.GetURL(),
+ URL: u.GetURL(),
Header: make(http.Header),
},
Timeout: 60 * time.Second,
@@ -380,16 +399,22 @@ func (h *HTTP) prepareBatchArray(
results := make([]*Response, reqCount)
for i, req := range requests {
+ resp := httpext.NewResponse(ctx)
parsedReq, err := h.parseBatchRequest(ctx, i, req)
if err != nil {
- return nil, nil, err
+ resp.Error = err.Error()
+ var k6e httpext.K6Error
+ if errors.As(err, &k6e) {
+ resp.ErrorCode = int(k6e.Code)
+ }
+ results[i] = h.responseFromHttpext(resp)
+ return batchReqs, results, err
}
- response := new(httpext.Response)
batchReqs[i] = httpext.BatchParsedHTTPRequest{
ParsedHTTPRequest: parsedReq,
- Response: response,
+ Response: resp,
}
- results[i] = h.responseFromHttpext(response)
+ results[i] = h.responseFromHttpext(resp)
}
return batchReqs, results, nil
@@ -404,16 +429,22 @@ func (h *HTTP) prepareBatchObject(
i := 0
for key, req := range requests {
+ resp := httpext.NewResponse(ctx)
parsedReq, err := h.parseBatchRequest(ctx, key, req)
if err != nil {
- return nil, nil, err
+ resp.Error = err.Error()
+ var k6e httpext.K6Error
+ if errors.As(err, &k6e) {
+ resp.ErrorCode = int(k6e.Code)
+ }
+ results[key] = h.responseFromHttpext(resp)
+ return batchReqs, results, err
}
- response := new(httpext.Response)
batchReqs[i] = httpext.BatchParsedHTTPRequest{
ParsedHTTPRequest: parsedReq,
- Response: response,
+ Response: resp,
}
- results[key] = h.responseFromHttpext(response)
+ results[key] = h.responseFromHttpext(resp)
i++
}
@@ -422,7 +453,7 @@ func (h *HTTP) prepareBatchObject(
// Batch makes multiple simultaneous HTTP requests. The provideds reqsV should be an array of request
// objects. Batch returns an array of responses and/or error
-func (h *HTTP) Batch(ctx context.Context, reqsV goja.Value) (goja.Value, error) {
+func (h *HTTP) Batch(ctx context.Context, reqsV goja.Value) (interface{}, error) {
state := lib.GetState(ctx)
if state == nil {
return nil, ErrBatchForbiddenInInitContext
@@ -444,7 +475,11 @@ func (h *HTTP) Batch(ctx context.Context, reqsV goja.Value) (goja.Value, error)
}
if err != nil {
- return nil, err
+ if state.Options.Throw.Bool {
+ return nil, err
+ }
+ state.Logger.WithField("error", err).Warn("A batch request failed")
+ return results, nil
}
reqCount := len(batchReqs)
@@ -459,20 +494,18 @@ func (h *HTTP) Batch(ctx context.Context, reqsV goja.Value) (goja.Value, error)
err = e
}
}
- return common.GetRuntime(ctx).ToValue(results), err
+ return results, err
}
func (h *HTTP) parseBatchRequest(
ctx context.Context, key interface{}, val interface{},
) (*httpext.ParsedHTTPRequest, error) {
var (
- method = HTTP_METHOD_GET
- ok bool
- err error
- reqURL httpext.URL
- body interface{}
- params goja.Value
- rt = common.GetRuntime(ctx)
+ method = HTTP_METHOD_GET
+ ok bool
+ body, reqURL interface{}
+ params goja.Value
+ rt = common.GetRuntime(ctx)
)
switch data := val.(type) {
@@ -486,10 +519,7 @@ func (h *HTTP) parseBatchRequest(
if !ok {
return nil, fmt.Errorf("invalid method type '%#v'", data[0])
}
- reqURL, err = ToURL(data[1])
- if err != nil {
- return nil, err
- }
+ reqURL = data[1]
if dataLen > 2 {
body = data[2]
}
@@ -499,12 +529,11 @@ func (h *HTTP) parseBatchRequest(
case map[string]interface{}:
// Handling of {method: "GET", url: "https://test.k6.io"}
- if murl, ok := data["url"]; !ok {
- return nil, fmt.Errorf("batch request %q doesn't have an url key", key)
- } else if reqURL, err = ToURL(murl); err != nil {
- return nil, err
+ if _, ok := data["url"]; !ok {
+ return nil, fmt.Errorf("batch request %v doesn't have a url key", key)
}
+ reqURL = data["url"]
body = data["body"] // It's fine if it's missing, the map lookup will return
if newMethod, ok := data["method"]; ok {
@@ -520,13 +549,8 @@ func (h *HTTP) parseBatchRequest(
if p, ok := data["params"]; ok {
params = rt.ToValue(p)
}
-
default:
- // Handling of "http://example.com/" or http.url`http://example.com/{$id}`
- reqURL, err = ToURL(data)
- if err != nil {
- return nil, err
- }
+ reqURL = val
}
return h.parseRequest(ctx, method, reqURL, body, params)
diff --git a/js/modules/k6/http/request_test.go b/js/modules/k6/http/request_test.go
index 0d9d7a653d0..55c0806d37e 100644
--- a/js/modules/k6/http/request_test.go
+++ b/js/modules/k6/http/request_test.go
@@ -572,6 +572,79 @@ func TestRequestAndBatch(t *testing.T) {
}
})
})
+ t.Run("InvalidURL", func(t *testing.T) {
+ t.Parallel()
+
+ expErr := `invalid URL: parse "https:// test.k6.io": invalid character " " in host name`
+ t.Run("throw=true", func(t *testing.T) {
+ js := `
+ http.request("GET", "https:// test.k6.io");
+ throw new Error("whoops!"); // shouldn't be reached
+ `
+ _, err := rt.RunString(js)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), expErr)
+ })
+
+ t.Run("throw=false", func(t *testing.T) {
+ state.Options.Throw.Bool = false
+ defer func() { state.Options.Throw.Bool = true }()
+
+ hook := logtest.NewLocal(state.Logger)
+ defer hook.Reset()
+
+ js := `
+ (function(){
+ var r = http.request("GET", "https:// test.k6.io");
+ return {error: r.error, error_code: r.error_code};
+ })()
+ `
+ ret, err := rt.RunString(js)
+ require.NoError(t, err)
+ require.NotNil(t, ret)
+ var retobj map[string]interface{}
+ var ok bool
+ if retobj, ok = ret.Export().(map[string]interface{}); !ok {
+ require.Fail(t, "got wrong return object: %#+v", retobj)
+ }
+ require.Equal(t, int64(1020), retobj["error_code"])
+ require.Equal(t, expErr, retobj["error"])
+
+ logEntry := hook.LastEntry()
+ require.NotNil(t, logEntry)
+ assert.Equal(t, logrus.WarnLevel, logEntry.Level)
+ assert.Contains(t, logEntry.Data["error"].(error).Error(), expErr)
+ assert.Equal(t, "Request Failed", logEntry.Message)
+ })
+
+ t.Run("throw=false,nopanic", func(t *testing.T) {
+ state.Options.Throw.Bool = false
+ defer func() { state.Options.Throw.Bool = true }()
+
+ hook := logtest.NewLocal(state.Logger)
+ defer hook.Reset()
+
+ js := `
+ (function(){
+ var r = http.request("GET", "https:// test.k6.io");
+ r.html();
+ r.json();
+ return r.error_code; // not reached because of json()
+ })()
+ `
+ ret, err := rt.RunString(js)
+ require.Error(t, err)
+ assert.Nil(t, ret)
+ assert.Contains(t, err.Error(), "unexpected end of JSON input")
+
+ logEntry := hook.LastEntry()
+ require.NotNil(t, logEntry)
+ assert.Equal(t, logrus.WarnLevel, logEntry.Level)
+ assert.Contains(t, logEntry.Data["error"].(error).Error(), expErr)
+ assert.Equal(t, "Request Failed", logEntry.Message)
+ })
+ })
+
t.Run("Unroutable", func(t *testing.T) {
_, err := rt.RunString(`http.request("GET", "http://sdafsgdhfjg/");`)
assert.Error(t, err)
@@ -1178,8 +1251,132 @@ func TestRequestAndBatch(t *testing.T) {
t.Run("Batch", func(t *testing.T) {
t.Run("error", func(t *testing.T) {
- _, err := rt.RunString(`var res = http.batch("https://somevalidurl.com");`)
- require.Error(t, err)
+ invalidURLerr := `invalid URL: parse "https:// invalidurl.com": invalid character " " in host name`
+ testCases := []struct {
+ name, code, expErr string
+ throw bool
+ }{
+ {
+ name: "invalid arg", code: `"https://somevalidurl.com"`,
+ expErr: `invalid http.batch() argument type string`, throw: true,
+ },
+ {
+ name: "invalid URL short", code: `["https:// invalidurl.com"]`,
+ expErr: invalidURLerr, throw: true,
+ },
+ {
+ name: "invalid URL short no throw", code: `["https:// invalidurl.com"]`,
+ expErr: invalidURLerr, throw: false,
+ },
+ {
+ name: "invalid URL array", code: `[ ["GET", "https:// invalidurl.com"] ]`,
+ expErr: invalidURLerr, throw: true,
+ },
+ {
+ name: "invalid URL array no throw", code: `[ ["GET", "https:// invalidurl.com"] ]`,
+ expErr: invalidURLerr, throw: false,
+ },
+ {
+ name: "invalid URL object", code: `[ {method: "GET", url: "https:// invalidurl.com"} ]`,
+ expErr: invalidURLerr, throw: true,
+ },
+ {
+ name: "invalid object no throw", code: `[ {method: "GET", url: "https:// invalidurl.com"} ]`,
+ expErr: invalidURLerr, throw: false,
+ },
+ {
+ name: "object no url key", code: `[ {method: "GET"} ]`,
+ expErr: `batch request 0 doesn't have a url key`, throw: true,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) { //nolint:paralleltest
+ oldThrow := state.Options.Throw.Bool
+ state.Options.Throw.Bool = tc.throw
+ defer func() { state.Options.Throw.Bool = oldThrow }()
+
+ hook := logtest.NewLocal(state.Logger)
+ defer hook.Reset()
+
+ ret, err := rt.RunString(fmt.Sprintf(`
+ (function(){
+ var r = http.batch(%s);
+ if (r.length !== 1) throw new Error('unexpected responses length: '+r.length);
+ return {error: r[0].error, error_code: r[0].error_code};
+ })()`, tc.code))
+ if tc.throw {
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), tc.expErr)
+ require.Nil(t, ret)
+ } else {
+ require.NoError(t, err)
+ require.NotNil(t, ret)
+ var retobj map[string]interface{}
+ var ok bool
+ if retobj, ok = ret.Export().(map[string]interface{}); !ok {
+ require.Fail(t, "got wrong return object: %#+v", retobj)
+ }
+ require.Equal(t, int64(1020), retobj["error_code"])
+ require.Equal(t, invalidURLerr, retobj["error"])
+
+ logEntry := hook.LastEntry()
+ require.NotNil(t, logEntry)
+ assert.Equal(t, logrus.WarnLevel, logEntry.Level)
+ assert.Contains(t, logEntry.Data["error"].(error).Error(), tc.expErr)
+ assert.Equal(t, "A batch request failed", logEntry.Message)
+ }
+ })
+ }
+ })
+ t.Run("error,nopanic", func(t *testing.T) { //nolint:paralleltest
+ invalidURLerr := `invalid URL: parse "https:// invalidurl.com": invalid character " " in host name`
+ testCases := []struct{ name, code string }{
+ {
+ name: "array", code: `[
+ ["GET", "https:// invalidurl.com"],
+ ["GET", "https://somevalidurl.com"],
+ ]`,
+ },
+ {
+ name: "object", code: `[
+ {method: "GET", url: "https:// invalidurl.com"},
+ {method: "GET", url: "https://somevalidurl.com"},
+ ]`,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) { //nolint:paralleltest
+ oldThrow := state.Options.Throw.Bool
+ state.Options.Throw.Bool = false
+ defer func() { state.Options.Throw.Bool = oldThrow }()
+
+ hook := logtest.NewLocal(state.Logger)
+ defer hook.Reset()
+
+ ret, err := rt.RunString(fmt.Sprintf(`
+ (function(){
+ var r = http.batch(%s);
+ if (r.length !== 2) throw new Error('unexpected responses length: '+r.length);
+ if (r[1] !== null) throw new Error('expected response at index 1 to be null');
+ r[0].html();
+ r[0].json();
+ return r[0].error_code; // not reached because of json()
+ })()
+ `, tc.code))
+ require.Error(t, err)
+ assert.Nil(t, ret)
+ assert.Contains(t, err.Error(), "unexpected end of JSON input")
+ logEntry := hook.LastEntry()
+ require.NotNil(t, logEntry)
+ assert.Equal(t, logrus.WarnLevel, logEntry.Level)
+ assert.Contains(t, logEntry.Data["error"].(error).Error(), invalidURLerr)
+ assert.Equal(t, "A batch request failed", logEntry.Message)
+ })
+ }
})
t.Run("GET", func(t *testing.T) {
_, err := rt.RunString(sr(`
diff --git a/lib/netext/httpext/error_codes.go b/lib/netext/httpext/error_codes.go
index d76bd4c6e69..a69dcdf5ae0 100644
--- a/lib/netext/httpext/error_codes.go
+++ b/lib/netext/httpext/error_codes.go
@@ -46,6 +46,7 @@ const (
// non specific
defaultErrorCode errCode = 1000
defaultNetNonTCPErrorCode errCode = 1010
+ invalidURLErrorCode errCode = 1020
requestTimeoutErrorCode errCode = 1050
// DNS errors
defaultDNSErrorCode errCode = 1100
@@ -101,6 +102,7 @@ const (
x509HostnameErrorCodeMsg = "x509: certificate doesn't match hostname"
x509UnknownAuthority = "x509: unknown authority"
requestTimeoutErrorCodeMsg = "request timeout"
+ invalidURLErrorCodeMsg = "invalid URL"
)
func http2ErrCodeOffset(code http2.ErrCode) errCode {
diff --git a/lib/netext/httpext/request.go b/lib/netext/httpext/request.go
index 3839d71e8d2..b48cd45a17b 100644
--- a/lib/netext/httpext/request.go
+++ b/lib/netext/httpext/request.go
@@ -30,7 +30,6 @@ import (
"net"
"net/http"
"net/http/cookiejar"
- "net/url"
"strconv"
"strings"
"time"
@@ -49,50 +48,6 @@ type HTTPRequestCookie struct {
Replace bool
}
-// A URL wraps net.URL, and preserves the template (if any) the URL was constructed from.
-type URL struct {
- u *url.URL
- Name string // http://example.com/thing/${}/
- URL string // http://example.com/thing/1234/
- CleanURL string // URL with masked user credentials, used for output
-}
-
-// NewURL returns a new URL for the provided url and name. The error is returned if the url provided
-// can't be parsed
-func NewURL(urlString, name string) (URL, error) {
- u, err := url.Parse(urlString)
- newURL := URL{u: u, Name: name, URL: urlString}
- newURL.CleanURL = newURL.Clean()
- if urlString == name {
- newURL.Name = newURL.CleanURL
- }
- return newURL, err
-}
-
-// Clean returns an output-safe representation of URL
-func (u URL) Clean() string {
- if u.CleanURL != "" {
- return u.CleanURL
- }
-
- if u.u == nil || u.u.User == nil {
- return u.URL
- }
-
- if password, passwordOk := u.u.User.Password(); passwordOk {
- // here 3 is for the '://' and 4 is because of '://' and ':' between the credentials
- return u.URL[:len(u.u.Scheme)+3] + "****:****" + u.URL[len(u.u.Scheme)+4+len(u.u.User.Username())+len(password):]
- }
-
- // here 3 in both places is for the '://'
- return u.URL[:len(u.u.Scheme)+3] + "****" + u.URL[len(u.u.Scheme)+3+len(u.u.User.Username()):]
-}
-
-// GetURL returns the internal url.URL
-func (u URL) GetURL() *url.URL {
- return u.u
-}
-
// Request represent an http request
type Request struct {
Method string `json:"method"`
diff --git a/lib/netext/httpext/response.go b/lib/netext/httpext/response.go
index 909cb7f235e..970c66c96c5 100644
--- a/lib/netext/httpext/response.go
+++ b/lib/netext/httpext/response.go
@@ -94,6 +94,14 @@ type Response struct {
Request Request `json:"request"`
}
+// NewResponse returns an empty Response instance.
+func NewResponse(ctx context.Context) *Response {
+ return &Response{
+ ctx: ctx,
+ Body: []byte{},
+ }
+}
+
func (res *Response) setTLSInfo(tlsState *tls.ConnectionState) {
tlsInfo, oscp := netext.ParseTLSConnState(tlsState)
res.TLSVersion = tlsInfo.Version
diff --git a/lib/netext/httpext/url.go b/lib/netext/httpext/url.go
new file mode 100644
index 00000000000..d6d37d87167
--- /dev/null
+++ b/lib/netext/httpext/url.go
@@ -0,0 +1,89 @@
+/*
+ *
+ * k6 - a next-generation load testing tool
+ * Copyright (C) 2016 Load Impact
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package httpext
+
+import (
+ "fmt"
+ "net/url"
+)
+
+// A URL wraps net.URL, and preserves the template (if any) the URL was constructed from.
+type URL struct {
+ u *url.URL
+ Name string // http://example.com/thing/${}/
+ URL string // http://example.com/thing/1234/
+ CleanURL string // URL with masked user credentials, used for output
+}
+
+// NewURL returns a new URL for the provided url and name. The error is returned if the url provided
+// can't be parsed
+func NewURL(urlString, name string) (URL, error) {
+ u, err := url.Parse(urlString)
+ if err != nil {
+ return URL{}, NewK6Error(invalidURLErrorCode,
+ fmt.Sprintf("%s: %s", invalidURLErrorCodeMsg, err), err)
+ }
+ newURL := URL{u: u, Name: name, URL: urlString}
+ newURL.CleanURL = newURL.Clean()
+ if urlString == name {
+ newURL.Name = newURL.CleanURL
+ }
+ return newURL, nil
+}
+
+// Clean returns an output-safe representation of URL
+func (u URL) Clean() string {
+ if u.CleanURL != "" {
+ return u.CleanURL
+ }
+
+ if u.u == nil || u.u.User == nil {
+ return u.URL
+ }
+
+ if password, passwordOk := u.u.User.Password(); passwordOk {
+ // here 3 is for the '://' and 4 is because of '://' and ':' between the credentials
+ return u.URL[:len(u.u.Scheme)+3] + "****:****" + u.URL[len(u.u.Scheme)+4+len(u.u.User.Username())+len(password):]
+ }
+
+ // here 3 in both places is for the '://'
+ return u.URL[:len(u.u.Scheme)+3] + "****" + u.URL[len(u.u.Scheme)+3+len(u.u.User.Username()):]
+}
+
+// GetURL returns the internal url.URL
+func (u URL) GetURL() *url.URL {
+ return u.u
+}
+
+// ToURL tries to convert anything passed to it to a k6 URL struct
+func ToURL(u interface{}) (URL, error) {
+ switch tu := u.(type) {
+ case URL:
+ // Handling of http.url`http://example.com/{$id}`
+ return tu, nil
+ case string:
+ // Handling of "http://example.com/"
+ return NewURL(tu, tu)
+ default:
+ return URL{}, NewK6Error(invalidURLErrorCode,
+ fmt.Sprintf("%s: '#%v'", invalidURLErrorCodeMsg, u), nil)
+ }
+}