-
Notifications
You must be signed in to change notification settings - Fork 50
feat: Introduce Context #287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,8 @@ import ( | |
|
||
var alphanumericRegex *regexp.Regexp | ||
var delimitingRegex *regexp.Regexp | ||
var goVersion string | ||
var goVersionOnce sync.Once | ||
|
||
func init() { | ||
alphanumericRegex = regexp.MustCompile(`^[a-zA-Z0-9]*$`) | ||
|
@@ -148,7 +150,7 @@ func (c *Client) SendRequest(method string, rawURL string, data url.Values, | |
} | ||
|
||
valueReader := &strings.Reader{} | ||
goVersion := runtime.Version() | ||
goVersion = getGoVersion() | ||
var req *http.Request | ||
|
||
//For HTTP GET Method there are no body parameters. All other parameters like query, path etc | ||
|
@@ -220,6 +222,89 @@ func (c *Client) SendRequest(method string, rawURL string, data url.Values, | |
return c.doWithErr(req) | ||
} | ||
|
||
func (c *Client) SendRequestWithContext(ctx context.Context, method string, rawURL string, data url.Values, | ||
headers map[string]interface{}, body ...byte) (*http.Response, error) { | ||
|
||
contentType := extractContentTypeHeader(headers) | ||
|
||
u, err := url.Parse(rawURL) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
valueReader := &strings.Reader{} | ||
goVersion := runtime.Version() | ||
manisha1997 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var req *http.Request | ||
|
||
//For HTTP GET Method there are no body parameters. All other parameters like query, path etc | ||
// are added as information in the url itself. Also while Content-Type is json, we are sending | ||
// json body. In that case, data variable contains all other parameters than body, which is the | ||
//same case as GET method. In that case as well all parameters will be added to url | ||
if method == http.MethodGet || method == http.MethodDelete || contentType == jsonContentType { | ||
if data != nil { | ||
v, _ := form.EncodeToStringWith(data, delimiter, escapee, keepZeros) | ||
s := delimitingRegex.ReplaceAllString(v, "") | ||
|
||
u.RawQuery = s | ||
} | ||
} | ||
|
||
//data is already processed and information will be added to u(the url) in the | ||
//previous step. Now body will solely contain json payload | ||
if contentType == jsonContentType { | ||
req, err = http.NewRequestWithContext(ctx, method, u.String(), bytes.NewBuffer(body)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} else { | ||
// Here the HTTP POST methods which do not have json content type are processed | ||
// All the values will be added in data and encoded (all body, query, path parameters) | ||
if method == http.MethodPost || method == http.MethodPut || method == http.MethodPatch { | ||
valueReader = strings.NewReader(data.Encode()) | ||
} | ||
req, err = http.NewRequestWithContext(context.Background(), method, u.String(), valueReader) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
} | ||
|
||
credErr := c.validateCredentials() | ||
if credErr != nil { | ||
return nil, credErr | ||
} | ||
if c.OAuth() == nil && c.Username != "" && c.Password != "" { | ||
req.SetBasicAuth(c.basicAuth()) | ||
} | ||
|
||
// E.g. "User-Agent": "twilio-go/1.0.0 (darwin amd64) go/go1.17.8" | ||
userAgentOnce.Do(func() { | ||
baseUserAgent = fmt.Sprintf("twilio-go/%s (%s %s) go/%s", LibraryVersion, runtime.GOOS, runtime.GOARCH, goVersion) | ||
}) | ||
userAgent := baseUserAgent | ||
|
||
if len(c.UserAgentExtensions) > 0 { | ||
userAgent += " " + strings.Join(c.UserAgentExtensions, " ") | ||
} | ||
if c.OAuth() != nil { | ||
oauth := c.OAuth() | ||
token, _ := c.OAuth().GetAccessToken(context.TODO()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should pass through the context here as well. |
||
if token != "" { | ||
req.Header.Add("Authorization", "Bearer "+token) | ||
} | ||
c.SetOauth(oauth) // Set the OAuth token in the client which gets nullified after the token fetch | ||
} else if c.Username != "" && c.Password != "" { | ||
req.SetBasicAuth(c.basicAuth()) | ||
} | ||
|
||
req.Header.Add("User-Agent", userAgent) | ||
|
||
for k, v := range headers { | ||
req.Header.Add(k, fmt.Sprint(v)) | ||
} | ||
return c.doWithErr(req) | ||
} | ||
|
||
// SetAccountSid sets the Client's accountSid field | ||
func (c *Client) SetAccountSid(sid string) { | ||
c.accountSid = sid | ||
|
@@ -237,3 +322,10 @@ func (c *Client) SetOauth(oauth OAuth) { | |
func (c *Client) OAuth() OAuth { | ||
return c.oAuth | ||
} | ||
|
||
func getGoVersion() string { | ||
goVersionOnce.Do(func() { | ||
goVersion = runtime.Version() | ||
}) | ||
return goVersion | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
package client | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"strings" | ||
|
@@ -25,13 +26,13 @@ func ReadLimits(pageSize *int, limit *int) int { | |
} | ||
} | ||
|
||
func GetNext(baseUrl string, response interface{}, getNextPage func(nextPageUri string) (interface{}, error)) (interface{}, error) { | ||
func GetNextWithContext(ctx context.Context, baseUrl string, response interface{}, getNextPage func(ctx context.Context, nextPageUri string) (interface{}, error)) (interface{}, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we need to add this as a second method, similar to the SendRequestWithContext, otherwise we're going to break everyone's clients. |
||
nextPageUrl, err := getNextPageUrl(baseUrl, response) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return getNextPage(nextPageUrl) | ||
return getNextPage(ctx, nextPageUrl) | ||
} | ||
|
||
func toMap(s interface{}) (map[string]interface{}, error) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
package client | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
|
@@ -14,6 +15,13 @@ type RequestHandler struct { | |
Region string | ||
} | ||
|
||
type RequestHandlerWithContext struct { | ||
Client BaseClient | ||
Edge string | ||
Region string | ||
ctx context.Context | ||
} | ||
|
||
func NewRequestHandler(client BaseClient) *RequestHandler { | ||
return &RequestHandler{ | ||
Client: client, | ||
|
@@ -22,7 +30,16 @@ func NewRequestHandler(client BaseClient) *RequestHandler { | |
} | ||
} | ||
|
||
func (c *RequestHandler) sendRequest(method string, rawURL string, data url.Values, | ||
func NewRequestHandlerWithContext(client BaseClient) *RequestHandlerWithContext { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure you need a separate struct for this honestly, just add a new method/methods on the RequestHandler and call it from everything. |
||
return &RequestHandlerWithContext{ | ||
Client: client, | ||
Edge: os.Getenv("TWILIO_EDGE"), | ||
Region: os.Getenv("TWILIO_REGION"), | ||
ctx: context.TODO(), | ||
} | ||
} | ||
|
||
func (c *RequestHandlerWithContext) sendRequest(method string, rawURL string, data url.Values, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think what you want here is to have the first method here be a context.Context and then you can edit the methods below to call this instead. |
||
headers map[string]interface{}, body ...byte) (*http.Response, error) { | ||
parsedURL, err := c.BuildUrl(rawURL) | ||
if err != nil { | ||
|
@@ -32,7 +49,7 @@ func (c *RequestHandler) sendRequest(method string, rawURL string, data url.Valu | |
} | ||
|
||
// BuildUrl builds the target host string taking into account region and edge configurations. | ||
func (c *RequestHandler) BuildUrl(rawURL string) (string, error) { | ||
func (c *RequestHandlerWithContext) BuildUrl(rawURL string) (string, error) { | ||
u, err := url.Parse(rawURL) | ||
if err != nil { | ||
return "", err | ||
|
@@ -82,22 +99,22 @@ func (c *RequestHandler) BuildUrl(rawURL string) (string, error) { | |
return u.String(), nil | ||
} | ||
|
||
func (c *RequestHandler) Post(path string, bodyData url.Values, headers map[string]interface{}, body ...byte) (*http.Response, error) { | ||
return c.sendRequest(http.MethodPost, path, bodyData, headers, body...) | ||
func (c *RequestHandlerWithContext) PostWithContext(ctx context.Context, path string, bodyData url.Values, headers map[string]interface{}, body ...byte) (*http.Response, error) { | ||
return c.Client.SendRequestWithContext(ctx, http.MethodPost, path, bodyData, headers, body...) | ||
} | ||
|
||
func (c *RequestHandler) Put(path string, bodyData url.Values, headers map[string]interface{}, body ...byte) (*http.Response, error) { | ||
return c.sendRequest(http.MethodPut, path, bodyData, headers, body...) | ||
func (c *RequestHandlerWithContext) PutWithContext(ctx context.Context, path string, bodyData url.Values, headers map[string]interface{}, body ...byte) (*http.Response, error) { | ||
return c.Client.SendRequestWithContext(ctx, http.MethodPut, path, bodyData, headers, body...) | ||
} | ||
|
||
func (c *RequestHandler) Patch(path string, bodyData url.Values, headers map[string]interface{}, body ...byte) (*http.Response, error) { | ||
return c.sendRequest(http.MethodPatch, path, bodyData, headers, body...) | ||
func (c *RequestHandlerWithContext) PatchWithContext(ctx context.Context, path string, bodyData url.Values, headers map[string]interface{}, body ...byte) (*http.Response, error) { | ||
return c.Client.SendRequestWithContext(ctx, http.MethodPatch, path, bodyData, headers, body...) | ||
} | ||
|
||
func (c *RequestHandler) Get(path string, queryData url.Values, headers map[string]interface{}) (*http.Response, error) { | ||
return c.sendRequest(http.MethodGet, path, queryData, headers) | ||
func (c *RequestHandlerWithContext) GetWithContext(ctx context.Context, path string, queryData url.Values, headers map[string]interface{}) (*http.Response, error) { | ||
return c.Client.SendRequestWithContext(ctx, http.MethodGet, path, queryData, headers) | ||
} | ||
|
||
func (c *RequestHandler) Delete(path string, queryData url.Values, headers map[string]interface{}) (*http.Response, error) { | ||
return c.sendRequest(http.MethodDelete, path, queryData, headers) | ||
func (c *RequestHandlerWithContext) DeleteWithContext(ctx context.Context, path string, queryData url.Values, headers map[string]interface{}) (*http.Response, error) { | ||
return c.Client.SendRequestWithContext(ctx, http.MethodDelete, path, queryData, headers) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK - I still think we should modify
SendRequest
to have no method body and just instead call