Skip to content
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

feat: Support JSON payload in HTTP requests #213

Merged
merged 10 commits into from
Oct 27, 2023
2 changes: 1 addition & 1 deletion client/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ type BaseClient interface {
AccountSid() string
SetTimeout(timeout time.Duration)
SendRequest(method string, rawURL string, data url.Values,
headers map[string]interface{}) (*http.Response, error)
headers map[string]interface{}, body ...byte) (*http.Response, error)
}
58 changes: 43 additions & 15 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import (
"encoding/json"
"fmt"
"net/http"
"bytes"
"net/url"
"regexp"
"runtime"
"strconv"
"strings"
"time"

"github.com/pkg/errors"
"github.com/twilio/twilio-go/client/form"
)
Expand Down Expand Up @@ -56,7 +56,17 @@ func (c *Client) SetTimeout(timeout time.Duration) {
c.HTTPClient.Timeout = timeout
}

func extractContentTypeHeader(headers map[string]interface{}) (cType string){
headerType, ok := headers["Content-Type"]
if !ok {
return urlEncodedContentType
}
return headerType.(string)
AsabuHere marked this conversation as resolved.
Show resolved Hide resolved
}

const (
urlEncodedContentType = "application/x-www-form-urlencoded"
jsonContentType = "application/json"
keepZeros = true
delimiter = '.'
escapee = '\\'
Expand Down Expand Up @@ -89,16 +99,24 @@ func (c *Client) doWithErr(req *http.Request) (*http.Response, error) {

// SendRequest verifies, constructs, and authorizes an HTTP request.
func (c *Client) SendRequest(method string, rawURL string, data url.Values,
headers map[string]interface{}) (*http.Response, error) {
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()
var req *http.Request

if method == http.MethodGet {
//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 conatins 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 || contentType == jsonContentType{
if data != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously only GET method was supposed to have query parameter, going forward any method can have query parameter.

v, _ := form.EncodeToStringWith(data, delimiter, escapee, keepZeros)
regex := regexp.MustCompile(`\.\d+`)
Expand All @@ -108,13 +126,28 @@ func (c *Client) SendRequest(method string, rawURL string, data url.Values,
}
}

if method == http.MethodPost {
valueReader = strings.NewReader(data.Encode())
//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.NewRequest(method, u.String(), bytes.NewBuffer(body))
if err != nil {
return nil, err
}
} else {
//Here the HTTP POST methods which is not having json content type are processed
//All the values will be added in data and encoded (all body, query, path parameters)
if method == http.MethodPost {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going forward PUT method can be used for update operation which will have request body.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should i add support for that as well now?

valueReader = strings.NewReader(data.Encode())
}
req, err = http.NewRequest(method, u.String(), valueReader)
if err != nil {
return nil, err
}

}

req, err := http.NewRequest(method, u.String(), valueReader)
if err != nil {
return nil, err
if contentType == urlEncodedContentType{
req.Header.Add("Content-Type", urlEncodedContentType)
}

req.SetBasicAuth(c.basicAuth())
Expand All @@ -128,14 +161,9 @@ func (c *Client) SendRequest(method string, rawURL string, data url.Values,

req.Header.Add("User-Agent", userAgent)

if method == http.MethodPost {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
}

for k, v := range headers {
req.Header.Add(k, fmt.Sprint(v))
}

return c.doWithErr(req)
}

Expand All @@ -147,4 +175,4 @@ func (c *Client) SetAccountSid(sid string) {
// Returns the Account SID.
func (c *Client) AccountSid() string {
return c.accountSid
}
}
9 changes: 4 additions & 5 deletions client/request_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@ func NewRequestHandler(client BaseClient) *RequestHandler {
}

func (c *RequestHandler) sendRequest(method string, rawURL string, data url.Values,
headers map[string]interface{}) (*http.Response, error) {
headers map[string]interface{}, body ...byte) (*http.Response, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of json body will data be null ? If yes, then try to check if method overload possible in go ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method overloading is not possible in go, in case of json body data need to be null, it can contain query parameters

parsedURL, err := c.BuildUrl(rawURL)
if err != nil {
return nil, err
}

return c.Client.SendRequest(method, parsedURL, data, headers)
return c.Client.SendRequest(method, parsedURL, data, headers, body...)
}

// BuildUrl builds the target host string taking into account region and edge configurations.
Expand Down Expand Up @@ -83,8 +82,8 @@ 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{}) (*http.Response, error) {
return c.sendRequest(http.MethodPost, path, bodyData, headers)
func (c *RequestHandler) Post(path string, bodyData url.Values, headers map[string]interface{}, body ...byte) (*http.Response, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to change method name as PUT operation will be allowed after some time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we change name now?

return c.sendRequest(http.MethodPost, path, bodyData, headers, body...)
}

func (c *RequestHandler) Get(path string, queryData url.Values, headers map[string]interface{}) (*http.Response, error) {
Expand Down