From e499a390788a65cb2255945649b6eed6273d3907 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sat, 24 Jun 2023 13:25:24 +0800 Subject: [PATCH] :recycle: refactor: refactoring request build and send logic --- README.md | 8 +- README.zh-CN.md | 6 +- builder.go | 467 +++++++++++++++++++++++ client.go | 525 ++++++++++++++++++++++++++ ext.go | 66 +++- go.mod | 8 +- go.sum | 18 +- greq.go | 32 ++ hireq_test.go => greq_test.go | 39 +- hireq.go | 685 ---------------------------------- middle.go | 8 +- option.go | 109 ++++++ req_body.go | 3 +- resp.go | 2 +- resp_test.go | 2 +- std.go | 106 +++--- std_test.go | 193 +++++----- 17 files changed, 1382 insertions(+), 895 deletions(-) create mode 100644 builder.go create mode 100644 client.go create mode 100644 greq.go rename hireq_test.go => greq_test.go (76%) delete mode 100644 hireq.go create mode 100644 option.go diff --git a/README.md b/README.md index 5e8c2ea..b2b91e2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# HReq +# Greq ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gookit/greq?style=flat-square) [![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/gookit/greq)](https://github.com/gookit/goutil) @@ -7,9 +7,7 @@ [![Unit-Tests](https://github.com/gookit/greq/workflows/Unit-Tests/badge.svg)](https://github.com/gookit/greq/actions) [![Coverage Status](https://coveralls.io/repos/github/gookit/greq/badge.svg?branch=main)](https://coveralls.io/github/gookit/greq?branch=main) -**HReq** A simple http client request builder and sender - -> `greq` inspired from [dghubble/sling][1] and more projects, please see refers. +**greq** A simple http client request builder and sender ## Features @@ -41,7 +39,7 @@ func main() { resp, err := greq.New("https://httpbin.org"). JSONType(). UserAgent("custom-client/1.0"). - PostDo("/post") + PostDo("/post", `{"name": "inhere"}`) if err != nil { panic(err) diff --git a/README.zh-CN.md b/README.zh-CN.md index b799128..2ce7580 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,4 +1,4 @@ -# HReq +# Greq ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gookit/greq?style=flat-square) [![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/gookit/greq)](https://github.com/gookit/goutil) @@ -7,9 +7,7 @@ [![Unit-Tests](https://github.com/gookit/greq/workflows/Unit-Tests/badge.svg)](https://github.com/gookit/greq/actions) [![Coverage Status](https://coveralls.io/repos/github/gookit/greq/badge.svg?branch=main)](https://coveralls.io/github/gookit/greq?branch=main) -**HReq** A simple http client request builder and sender - -> `greq` inspired from https://github.com/dghubble/sling and more projects, please see refers. +**greq** A simple http client request builder and sender ## 功能说明 diff --git a/builder.go b/builder.go new file mode 100644 index 0000000..e4a4ee7 --- /dev/null +++ b/builder.go @@ -0,0 +1,467 @@ +package greq + +import ( + "bytes" + "io" + "net/http" + gourl "net/url" + "os" + "strings" + + "github.com/gookit/goutil/basefn" + "github.com/gookit/goutil/fsutil" + "github.com/gookit/goutil/netutil/httpctype" + "github.com/gookit/goutil/netutil/httpheader" + "github.com/gookit/goutil/netutil/httpreq" + "github.com/gookit/goutil/strutil" +) + +const ( + HeaderUAgent = "User-Agent" + HeaderAuth = "Authorization" + + AgentCURL = "CURL/7.64.1 greq/1.0.2" +) + +// Builder is a http request builder. +type Builder struct { + *Options + // client for send request. if not set, will use default client. + cli *Client +} + +// NewBuilder for request +func NewBuilder(fns ...OptionFn) *Builder { + opt := &Options{ + // Method: http.MethodGet, + Header: make(http.Header), + HeaderM: make(map[string]string), + Query: make(gourl.Values), + } + for _, fn := range fns { + fn(opt) + } + return &Builder{Options: opt} +} + +// BuilderWithClient create a new builder with client +func BuilderWithClient(c *Client) *Builder { + return NewBuilder().WithClient(c) +} + +func newBuilder(c *Client, method, pathURL string) *Builder { + b := NewBuilder() + b.cli = c + b.Method = method + b.pathURL = pathURL + return b +} + +// WithClient set cli to builder +func (b *Builder) WithClient(c *Client) *Builder { + b.cli = c + return b +} + +// WithOptionFn set option fns to builder +func (b *Builder) WithOptionFn(fns ...OptionFn) *Builder { + return b.WithOptionFns(fns) +} + +// WithOptionFns set option fns to builder +func (b *Builder) WithOptionFns(fns []OptionFn) *Builder { + for _, fn := range fns { + fn(b.Options) + } + return b +} + +// WithMethod set request method name. +func (b *Builder) WithMethod(method string) *Builder { + b.Method = method + return b +} + +// PathURL set path URL for current request +func (b *Builder) PathURL(pathURL string) *Builder { + b.pathURL = pathURL + return b +} + +// +// +// ----------- URL, Query params ------------ +// +// + +// AddQuery appends new k-v param to the Query string. +func (b *Builder) AddQuery(key string, value any) *Builder { + b.Query.Add(key, strutil.SafeString(value)) + return b +} + +// QueryParams appends url.Values/map[string]string to the Query string. +// The value will be encoded as url Query parameters on send requests (see Send()). +func (b *Builder) QueryParams(ps any) *Builder { + if ps != nil { + queryValues := httpreq.ToQueryValues(ps) + + for key, values := range queryValues { + for _, value := range values { + b.Query.Add(key, value) + } + } + } + + return b +} + +// QueryValues appends url.Values to the Query string. +// The value will be encoded as url Query parameters on new requests (see Send()). +func (b *Builder) QueryValues(values gourl.Values) *Builder { + return b.QueryParams(values) +} + +// +// +// ----------- HeaderM ------------ +// +// + +// AddHeader adds the key, value pair in HeaderM, appending values for existing keys +// to the key's values. Header keys are canonicalized. +func (b *Builder) AddHeader(key, value string) *Builder { + b.Header.Add(key, value) + return b +} + +// SetHeader sets the key, value pair in HeaderM, replacing existing values +// associated with key. Header keys are canonicalized. +func (b *Builder) SetHeader(key, value string) *Builder { + b.Header.Set(key, value) + return b +} + +// AddHeaders adds all the http.Header values, appending values for existing keys +// to the key's values. Header keys are canonicalized. +func (b *Builder) AddHeaders(headers http.Header) *Builder { + for key, values := range headers { + for i := range values { + b.Header.Add(key, values[i]) + } + } + return b +} + +// AddHeaderMap sets all the http.Header values, appending values for existing keys +// to the key's values. +func (b *Builder) AddHeaderMap(headers map[string]string) *Builder { + for key, value := range headers { + b.Header.Add(key, value) + } + return b +} + +// SetHeaders sets all the http.Header values, replacing values for existing keys +// to the key's values. Header keys are canonicalized. +func (b *Builder) SetHeaders(headers http.Header) *Builder { + for key, values := range headers { + for i := range values { + if i == 0 { + b.Header.Set(key, values[i]) + } else { + b.Header.Add(key, values[i]) + } + } + } + return b +} + +// SetHeaderMap sets all the http.Header values, replacing values for existing keys +// to the key's values. +func (b *Builder) SetHeaderMap(headers map[string]string) *Builder { + for key, value := range headers { + b.Header.Set(key, value) + } + return b +} + +// UserAgent set User-Agent header +func (b *Builder) UserAgent(ua string) *Builder { + b.HeaderM[httpheader.UserAgent] = ua + return b +} + +// UserAuth with user auth header value. +func (b *Builder) UserAuth(value string) *Builder { + return b.SetHeader(httpheader.UserAuth, value) +} + +// BasicAuth sets the Authorization header to use HTTP Basic Authentication +// with the provided username and password. +// +// With HTTP Basic Authentication the provided username and password are not encrypted. +func (b *Builder) BasicAuth(username, password string) *Builder { + return b.SetHeader(httpheader.UserAuth, httpreq.BuildBasicAuth(username, password)) +} + +// +// +// ----------- Cookies ------------ +// +// + +// WithCookies to request +func (b *Builder) WithCookies(hcs ...*http.Cookie) *Builder { + var sb strings.Builder + for i, hc := range hcs { + if i > 0 { + sb.WriteByte(';') + } + sb.WriteString(hc.String()) + } + + return b.WithCookieString(sb.String()) +} + +// WithCookieString set cookie header value. +// +// Usage: +// +// h.NewOpt(). +// WithCookieString("name=inhere;age=30"). +// Do("/some/api", "GET") +func (b *Builder) WithCookieString(value string) *Builder { + // return h.AddHeader("Set-Cookie", value) // response + return b.AddHeader("Cookie", value) +} + +// +// +// ----------- Content Type ------------ +// +// + +// WithContentType with custom ContentType header +func (b *Builder) WithContentType(value string) *Builder { + b.ContentType = value + return b +} + +// WithType with custom ContentType header +func (b *Builder) WithType(value string) *Builder { + b.ContentType = value + return b +} + +// XMLType with xml Content-Type header +func (b *Builder) XMLType() *Builder { + b.ContentType = httpctype.XML + return b +} + +// JSONType with json Content-Type header +func (b *Builder) JSONType() *Builder { + b.ContentType = httpctype.JSON + return b +} + +// FormType with from Content-Type header +func (b *Builder) FormType() *Builder { + b.ContentType = httpctype.Form + return b +} + +// MultipartType with multipart/form-data Content-Type header +func (b *Builder) MultipartType() *Builder { + b.ContentType = httpctype.FormData + return b +} + +// +// +// ----------- Request Body ------------ +// +// + +// WithBody with custom any type body +func (b *Builder) WithBody(body any) *Builder { + return b.AnyBody(body) +} + +// AnyBody with custom any type body +func (b *Builder) AnyBody(body any) *Builder { + if bp, ok := body.(BodyProvider); ok { + return b.BodyProvider(bp) + } + + b.Body = body + return b +} + +// BodyProvider with custom body provider +func (b *Builder) BodyProvider(bp BodyProvider) *Builder { + b.Provider = bp + return b +} + +// BodyReader with custom io reader body +func (b *Builder) BodyReader(r io.Reader) *Builder { + b.Provider = bodyProvider{body: r} + return b +} + +// FileContentsBody read file contents as body +func (b *Builder) FileContentsBody(filePath string) *Builder { + file, err := os.OpenFile(filePath, os.O_RDONLY, fsutil.DefaultFilePerm) + if err != nil { + panic(err) + } + return b.BodyReader(file) +} + +// JSONBody with JSON data body +func (b *Builder) JSONBody(jsonData any) *Builder { + b.Provider = jsonBodyProvider{ + payload: jsonData, + } + return b +} + +// FormBody with form data body +func (b *Builder) FormBody(formData any) *Builder { + b.Provider = formBodyProvider{ + payload: formData, + } + return b +} + +// BytesBody with custom string body +func (b *Builder) BytesBody(bs []byte) *Builder { + return b.BodyReader(bytes.NewReader(bs)) +} + +// StringBody with custom string body +func (b *Builder) StringBody(s string) *Builder { + return b.BodyReader(strings.NewReader(s)) +} + +// Multipart with custom multipart body +func (b *Builder) Multipart(key, value string) *Builder { + // TODO + return b +} + +// +// +// ----------- Build Request ------------ +// +// + +// Build request +func (b *Builder) Build(method, pathURL string) (*http.Request, error) { + b.Method = method + cli := basefn.OrValue(b.cli == nil, std, b.cli) + return cli.NewRequestWithOptions(pathURL, b.Options) +} + +// String request to string. +func (b *Builder) String() string { + r, err := b.Build(b.Method, b.pathURL) + if err != nil { + return "" + } + return httpreq.RequestToString(r) +} + +// +// +// ----------- Send Request ------------ +// +// + +// Get sets the method to GET and sets the given pathURL +func (b *Builder) Get(pathURL string, optFns ...OptionFn) *Builder { + b.pathURL = pathURL + b.Method = http.MethodGet + b.WithOptionFns(optFns) + return b +} + +// GetDo sets the method to GET and sets the given pathURL, +// then send request and return response. +func (b *Builder) GetDo(pathURL string, optFns ...OptionFn) (*Response, error) { + return b.Send(http.MethodGet, pathURL, optFns...) +} + +// Post sets the method to POST and sets the given pathURL +func (b *Builder) Post(pathURL string, data any, optFns ...OptionFn) *Builder { + b.pathURL = pathURL + b.Method = http.MethodPost + b.Body = data + b.WithOptionFns(optFns) + return b +} + +// PostDo sets the method to POST and sets the given pathURL, +// then send request and return http response. +func (b *Builder) PostDo(pathURL string, data any, optFns ...OptionFn) (*Response, error) { + return b.Post(pathURL, data, optFns...).Do() +} + +// Put sets the method to PUT and sets the given pathURL +func (b *Builder) Put(pathURL string, data any, optFns ...OptionFn) *Builder { + b.Body = data + b.pathURL = pathURL + b.Method = http.MethodPut + b.WithOptionFns(optFns) + return b +} + +// PutDo sets the method to PUT and sets the given pathURL, then send request and return response. +func (b *Builder) PutDo(pathURL string, data any, optFns ...OptionFn) (*Response, error) { + return b.Put(pathURL, data, optFns...).Do() +} + +// Patch sets the method to PATCH and sets the given pathURL +func (b *Builder) Patch(pathURL string, data any, optFns ...OptionFn) *Builder { + b.Body = data + b.pathURL = pathURL + b.Method = http.MethodPatch + b.WithOptionFns(optFns) + return b +} + +// PatchDo sets the method to PATCH and sets the given pathURL, then send request and return response. +func (b *Builder) PatchDo(pathURL string, data any, optFns ...OptionFn) (*Response, error) { + return b.Patch(pathURL, data, optFns...).Do() +} + +// Delete sets the method to DELETE and sets the given pathURL +func (b *Builder) Delete(pathURL string, optFns ...OptionFn) *Builder { + b.pathURL = pathURL + b.Method = http.MethodDelete + b.WithOptionFns(optFns) + return b +} + +// DeleteDo sets the method to DELETE and sets the given pathURL, then send request and return response. +func (b *Builder) DeleteDo(pathURL string, optFns ...OptionFn) (*Response, error) { + return b.Send(http.MethodDelete, pathURL, optFns...) +} + +// Do send request and return response. +func (b *Builder) Do(optFns ...OptionFn) (*Response, error) { + return b.Send(b.Method, b.pathURL, optFns...) +} + +// Send request and return response, alias of Do() +func (b *Builder) Send(method, url string, optFns ...OptionFn) (*Response, error) { + if len(optFns) > 0 { + b.WithOptionFns(optFns) + } + + b.Method = method + cli := basefn.OrValue(b.cli == nil, std, b.cli) + return cli.SendWithOpt(url, b.Options) +} diff --git a/client.go b/client.go new file mode 100644 index 0000000..a8eef33 --- /dev/null +++ b/client.go @@ -0,0 +1,525 @@ +package greq + +import ( + "context" + "io" + "net/http" + gourl "net/url" + "strings" + + "github.com/gookit/goutil/basefn" + "github.com/gookit/goutil/netutil/httpctype" + "github.com/gookit/goutil/netutil/httpheader" + "github.com/gookit/goutil/netutil/httpreq" + "github.com/gookit/goutil/strutil" +) + +// Client is an HTTP Request builder and sender. +type Client struct { + doer httpreq.Doer + // core handler. + handler HandleFunc + middles []Middleware + // Vars template vars for URL, Header, Query, Body + // + // eg: http://example.com/{name} + Vars map[string]string + + // BeforeSend callback + BeforeSend func(r *http.Request) + // AfterSend callback + AfterSend AfterSendFn + + // + // default options for all requests + // + + // http method eg: GET,POST + method string + baseURL string + header http.Header + // Query params data. allow: map[string]string, url.Values + query gourl.Values + // content type + ContentType string + // response data decoder + respDecoder RespDecoder +} + +// New create +func New(baseURL ...string) *Client { + h := &Client{ + doer: &http.Client{}, + method: http.MethodGet, + header: make(http.Header), + query: make(gourl.Values), + // default use JSON decoder + respDecoder: jsonDecoder{}, + } + + if len(baseURL) > 0 { + h.baseURL = baseURL[0] + } + return h +} + +// Sub create an instance from current. +func (h *Client) Sub() *Client { + // copy HeaderM pairs into new Header map + headerCopy := make(http.Header) + for k, v := range h.header { + headerCopy[k] = v + } + + return &Client{ + doer: h.doer, + method: h.method, + baseURL: h.baseURL, + header: headerCopy, + // query: append([]any{}, s.query...), + respDecoder: h.respDecoder, + } +} + +// ------------ Config ------------ + +// Doer custom set http request doer. +// If a nil cli is given, the DefaultDoer will be used. +func (h *Client) Doer(doer httpreq.Doer) *Client { + if doer != nil { + h.doer = doer + } else { + h.doer = DefaultDoer + } + return h +} + +// Client custom set http request doer +func (h *Client) Client(doer httpreq.Doer) *Client { + return h.Doer(doer) +} + +// HttpClient custom set http cli as request doer +func (h *Client) HttpClient(hClient *http.Client) *Client { + return h.Doer(hClient) +} + +// Config custom config http request doer +func (h *Client) Config(fn func(doer httpreq.Doer)) *Client { + fn(h.doer) + return h +} + +// ConfigHClient custom config http cli. +// +// Usage: +// +// h.ConfigHClient(func(hClient *http.Client) { +// hClient.Timeout = 30 * time.Second +// }) +func (h *Client) ConfigHClient(fn func(hClient *http.Client)) *Client { + if hc, ok := h.doer.(*http.Client); ok { + fn(hc) + } else { + panic("the doer is not an *http.Client") + } + + return h +} + +// Use one or multi middlewares +func (h *Client) Use(middles ...Middleware) *Client { + return h.Middlewares(middles...) +} + +// Uses one or multi middlewares +func (h *Client) Uses(middles ...Middleware) *Client { + return h.Middlewares(middles...) +} + +// Middleware add one or multi middlewares +func (h *Client) Middleware(middles ...Middleware) *Client { + return h.Middlewares(middles...) +} + +// Middlewares add one or multi middlewares +func (h *Client) Middlewares(middles ...Middleware) *Client { + h.middles = append(h.middles, middles...) + return h +} + +// WithRespDecoder for cli +func (h *Client) WithRespDecoder(respDecoder RespDecoder) *Client { + h.respDecoder = respDecoder + return h +} + +// OnBeforeSend for cli +func (h *Client) OnBeforeSend(fn func(r *http.Request)) *Client { + h.BeforeSend = fn + return h +} + +// ----------- Header ------------ + +// DefaultHeader sets the http.Header value, it will be used for all requests. +func (h *Client) DefaultHeader(key, value string) *Client { + h.header.Set(key, value) + return h +} + +// DefaultHeaders sets all the http.Header values, it will be used for all requests. +func (h *Client) DefaultHeaders(headers http.Header) *Client { + h.header = headers + return h +} + +// DefaultContentType set default ContentType header, it will be used for all requests. +// +// Usage: +// +// // json type +// h.DefaultContentType(httpctype.JSON) +// // form type +// h.DefaultContentType(httpctype.Form) +func (h *Client) DefaultContentType(value string) *Client { + h.ContentType = value + return h +} + +// DefaultUserAgent with User-Agent header setting for all requests. +func (h *Client) DefaultUserAgent(value string) *Client { + return h.DefaultHeader(httpheader.UserAgent, value) +} + +// DefaultUserAuth with user auth header value for all requests. +func (h *Client) DefaultUserAuth(value string) *Client { + return h.DefaultHeader(httpheader.UserAuth, value) +} + +// DefaultBasicAuth sets the Authorization header to use HTTP Basic Authentication +// with the provided username and password. With HTTP Basic Authentication +// the provided username and password are not encrypted. +func (h *Client) DefaultBasicAuth(username, password string) *Client { + return h.DefaultHeader(httpheader.UserAuth, httpreq.BuildBasicAuth(username, password)) +} + +// ------------ Method ------------ + +// BaseURL set default base URL for all request +func (h *Client) BaseURL(baseURL string) *Client { + h.baseURL = baseURL + return h +} + +// DefaultMethod set default method name. it will be used when the Get()/Post() method is empty. +func (h *Client) DefaultMethod(method string) *Client { + h.method = method + return h +} + +// +// ------------ REST requests ------------ +// + +// Head sets the method to HEAD and request the pathURL, then send request and return response. +func (h *Client) Head(pathURL string) *Builder { + return newBuilder(h, http.MethodHead, pathURL) +} + +// HeadDo sets the method to HEAD and request the pathURL, +// then send request and return response. +func (h *Client) HeadDo(pathURL string, withOpt ...*Options) (*Response, error) { + opt := basefn.FirstOr(withOpt, &Options{}) + return h.SendWithOpt(pathURL, opt.WithMethod(http.MethodHead)) +} + +// Get sets the method to GET and sets the given pathURL +func (h *Client) Get(pathURL string) *Builder { + return newBuilder(h, http.MethodGet, pathURL) +} + +// GetDo sets the method to GET and sets the given pathURL, +// then send request and return response. +func (h *Client) GetDo(pathURL string, optFns ...OptionFn) (*Response, error) { + return h.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodGet)) +} + +// Post sets the method to POST and sets the given pathURL +func (h *Client) Post(pathURL string) *Builder { + return newBuilder(h, http.MethodPost, pathURL) +} + +// PostDo sets the method to POST and sets the given pathURL, +// then send request and return http response. +func (h *Client) PostDo(pathURL string, optFns ...OptionFn) (*Response, error) { + return h.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodPost)) +} + +// Put sets the method to PUT and sets the given pathURL +func (h *Client) Put(pathURL string) *Builder { + return newBuilder(h, http.MethodPut, pathURL) +} + +// PutDo sets the method to PUT and sets the given pathURL, +// then send request and return http response. +func (h *Client) PutDo(pathURL string, optFns ...OptionFn) (*Response, error) { + return h.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodPut)) +} + +// Patch sets the method to PATCH and sets the given pathURL +func (h *Client) Patch(pathURL string) *Builder { + return newBuilder(h, http.MethodPatch, pathURL) +} + +// PatchDo sets the method to PATCH and sets the given pathURL, +// then send request and return http response. +func (h *Client) PatchDo(pathURL string, optFns ...OptionFn) (*Response, error) { + return h.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodPatch)) +} + +// Delete sets the method to DELETE and sets the given pathURL +func (h *Client) Delete(pathURL string) *Builder { + return newBuilder(h, http.MethodDelete, pathURL) +} + +// DeleteDo sets the method to DELETE and sets the given pathURL, +// then send request and return http response. +func (h *Client) DeleteDo(pathURL string, optFns ...OptionFn) (*Response, error) { + return h.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodDelete)) +} + +// ----------- URL, Query params ------------ + +// JSONType with json Content-Type header +func (h *Client) JSONType() *Builder { + return BuilderWithClient(h).JSONType() +} + +// FormType with from Content-Type header +func (h *Client) FormType() *Builder { + return BuilderWithClient(h).FormType() +} + +// UserAgent with User-Agent header setting for all requests. +func (h *Client) UserAgent(value string) *Builder { + return BuilderWithClient(h).UserAgent(value) +} + +// UserAuth with user auth header value for all requests. +func (h *Client) UserAuth(value string) *Builder { + return BuilderWithClient(h).UserAuth(value) +} + +// BasicAuth sets the Authorization header to use HTTP Basic Authentication +// with the provided username and password. +// +// With HTTP Basic Authentication the provided username and password are not encrypted. +func (h *Client) BasicAuth(username, password string) *Builder { + return BuilderWithClient(h).BasicAuth(username, password) +} + +// +// +// ----------- URL, Query params ------------ +// +// + +// QueryParams appends url.Values/map[string]string to the Query string. +// The value will be encoded as url Query parameters on send requests (see Send()). +func (h *Client) QueryParams(ps any) *Builder { + return BuilderWithClient(h).QueryParams(ps) +} + +// ----------- Request Body ------------ + +// Body with custom any type body +func (h *Client) Body(body any) *Builder { + return BuilderWithClient(h).AnyBody(body) +} + +// AnyBody with custom any type body +func (h *Client) AnyBody(body any) *Builder { + return BuilderWithClient(h).AnyBody(body) +} + +// BodyReader with custom io reader body +func (h *Client) BodyReader(r io.Reader) *Builder { + return BuilderWithClient(h).BodyReader(r) +} + +// BodyProvider with custom body provider +func (h *Client) BodyProvider(bp BodyProvider) *Builder { + b := BuilderWithClient(h) + b.Provider = bp + return b +} + +// ----------- Do send request ------------ + +// Do send request and return response +func (h *Client) Do(method, url string, optFns ...OptionFn) (*Response, error) { + return h.SendWithOption(method, url, optFns...) +} + +// Send request and return response, alias of Do() +func (h *Client) Send(method, url string, optFns ...OptionFn) (*Response, error) { + return h.SendWithOption(method, url, optFns...) +} + +// MustSend send request and return response, will panic on error +func (h *Client) MustSend(method, url string, optFns ...OptionFn) *Response { + resp, err := h.SendWithOption(method, url, optFns...) + if err != nil { + panic(err) + } + return resp +} + +// SendRaw http request text. +// +// Format: +// +// POST https://example.com/path?name=inhere +// Content-Type: application/json +// Accept: */* +// +// +func (h *Client) SendRaw(raw string, varMp map[string]string) (*Response, error) { + method := "GET" + reqUrl := "TODO" + + var body io.Reader + req, err := http.NewRequest(method, reqUrl, body) + if err != nil { + return nil, err + } + + return h.SendRequest(req) +} + +// DoWithOption request with options, then return response +func (h *Client) DoWithOption(method, url string, optFns ...OptionFn) (*Response, error) { + return h.SendWithOption(method, url, optFns...) +} + +// SendWithOption request with options, then return response +func (h *Client) SendWithOption(method, url string, optFns ...OptionFn) (*Response, error) { + return h.SendWithOpt(url, NewOpt2(optFns, method)) +} + +// SendWithOpt send request with option, then return response +func (h *Client) SendWithOpt(pathURL string, opt *Options) (*Response, error) { + // build request + req, err := h.NewRequestWithOptions(pathURL, opt) + if err != nil { + return nil, err + } + + // do send + return h.SendRequest(req) +} + +// SendRequest send request +func (h *Client) SendRequest(req *http.Request) (*Response, error) { + // wrap middlewares + h.wrapMiddlewares() + + // call before send. + if h.BeforeSend != nil { + h.BeforeSend(req) + } + + // do send by core handler + resp, err := h.handler(req) + + // call after send. + if h.AfterSend != nil { + h.AfterSend(resp, err) + } + return resp, err +} + +// ----------- Build request ------------ + +// NewRequest build new request +func (h *Client) NewRequest(method, url string, optFns ...OptionFn) (*http.Request, error) { + return h.NewRequestWithOptions(url, NewOpt2(optFns, method)) +} + +// NewRequestWithOptions build new request with Options +func (h *Client) NewRequestWithOptions(url string, opt *Options) (*http.Request, error) { + fullURL := url + + if len(h.baseURL) > 0 { + if !strings.HasPrefix(url, "http") { + fullURL = h.baseURL + url + } else if len(url) == 0 { + fullURL = h.baseURL + } + } + + opt = orCreate(opt) + ctx := opt.Context + if ctx == nil { + ctx = context.Background() + } + + // append Query params + qm := MergeURLValues(h.query, opt.Query) + if len(qm) > 0 { + fullURL = httpreq.AppendQueryToURLString(fullURL, qm) + } + + // make body + var err error + var body io.Reader + if opt.Provider != nil { + body, err = opt.Provider.Body() + if err != nil { + return nil, err + } + + if bpTyp := opt.Provider.ContentType(); bpTyp != "" { + opt.ContentType = bpTyp + } + } + + cType := strutil.OrElse(opt.ContentType, h.ContentType) + method := strings.ToUpper(strutil.OrElse(opt.Method, h.method)) + + if opt.Data != nil { + if httpreq.IsNoBodyMethod(method) { + body = nil + fullURL = httpreq.AppendQueryToURLString(fullURL, httpreq.MakeQuery(opt.Data)) + } else if body == nil { + cType := strutil.OrElse(opt.HeaderM[httpctype.Key], cType) + body = httpreq.MakeBody(opt.Data, cType) + } + } + + req, err := http.NewRequestWithContext(ctx, method, fullURL, body) + if err != nil { + return nil, err + } + + // copy and set headers + SetHeaders(req, h.header, opt.Header) + if len(opt.HeaderM) > 0 { + SetHeaderMap(req, opt.HeaderM) + } + if len(cType) > 0 { + req.Header.Set(httpheader.ContentType, cType) + } + + return req, err +} + +// String request to string. +func (h *Client) String() string { + r, err := h.NewRequest("", "") + if err != nil { + return "" + } + return httpreq.RequestToString(r) +} diff --git a/ext.go b/ext.go index 36a8364..2f02aae 100644 --- a/ext.go +++ b/ext.go @@ -1,23 +1,63 @@ package greq import ( - "context" - "time" + "net/http" + gourl "net/url" + + "github.com/gookit/goutil/arrutil" ) -func ensureOpt(opt *ReqOption) *ReqOption { - if opt == nil { - opt = &ReqOption{} +// SetHeaders sets the key, value pairs from the given http.Header to the +// request. Values for existing keys are overwritten. +// +// TODO replace to goutil/netutil/httpreq.SetHeaders +func SetHeaders(req *http.Request, headers ...http.Header) { + for _, header := range headers { + for key, values := range header { + req.Header[key] = values + } } - if opt.Context == nil { - opt.Context = context.Background() +} + +// SetHeaderMap to reqeust instance. +// +// TODO replace to goutil/netutil/httpreq.SetHeaderMap +func SetHeaderMap(req *http.Request, headerMap map[string]string) { + for k, v := range headerMap { + req.Header.Set(k, v) } +} - if opt.Timeout > 0 { - opt.Context, opt.TCancelFunc = context.WithTimeout( - opt.Context, - time.Duration(opt.Timeout)*time.Millisecond, - ) +// MergeURLValues merge url.Values by overwrite. +// +// values support: url.Values, map[string]string, map[string][]string +// +// TODO replace to goutil/netutil/httpreq.MergeURLValues +func MergeURLValues(uv gourl.Values, values ...any) gourl.Values { + if uv == nil { + uv = make(gourl.Values) } - return opt + + for _, v := range values { + switch tv := v.(type) { + case gourl.Values: + for k, vs := range tv { + uv[k] = vs + } + case map[string]any: + for k, v := range tv { + uv[k] = arrutil.AnyToStrings(v) + } + case map[string]string: + for k, v := range tv { + uv[k] = []string{v} + } + case map[string][]string: + for k, vs := range tv { + uv[k] = vs + } + } + } + + return uv } diff --git a/go.mod b/go.mod index d8d16f6..a032264 100644 --- a/go.mod +++ b/go.mod @@ -2,12 +2,12 @@ module github.com/gookit/greq go 1.19 -require github.com/gookit/goutil v0.6.8 +require github.com/gookit/goutil v0.6.10 require ( github.com/gookit/color v1.5.3 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/text v0.10.0 // indirect ) diff --git a/go.sum b/go.sum index 527a5dc..e1902a2 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE= github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE= -github.com/gookit/goutil v0.6.8 h1:B2XXSCGav5TXWtKRT9i/s/owOLXXB7sY6UsfqeSLroE= -github.com/gookit/goutil v0.6.8/go.mod h1:u+Isykc6RQcZ4GQzulsaGm+Famd97U5Tzp3aQyo+jyA= +github.com/gookit/goutil v0.6.10 h1:iq7CXOf+fYLvrVAh3+ZoLgufGfK65TwbzE8NpnPGtyk= +github.com/gookit/goutil v0.6.10/go.mod h1:qqrPoX+Pm6YmxqqccgkNLPirTFX7UYMES1SK+fokqQU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/greq.go b/greq.go new file mode 100644 index 0000000..5ae2334 --- /dev/null +++ b/greq.go @@ -0,0 +1,32 @@ +// Package greq is a simple http cli request builder, inspired from https://github.com/dghubble/sling +package greq + +import ( + "io" + "net/http" +) + +// DefaultDoer for request. +var DefaultDoer = http.DefaultClient + +// HandleFunc for the Middleware +type HandleFunc func(r *http.Request) (*Response, error) + +// AfterSendFn callback func +type AfterSendFn func(resp *Response, err error) + +// RequestCreator interface +type RequestCreator interface { + NewRequest(method, target string, body io.Reader) *http.Request +} + +// RequestCreatorFunc func +type RequestCreatorFunc func(method, target string, body io.Reader) *http.Request + +// Must return response, if error will panic +func Must(w *Response, err error) *Response { + if err != nil { + panic(err) + } + return w +} diff --git a/hireq_test.go b/greq_test.go similarity index 76% rename from hireq_test.go rename to greq_test.go index 3db51f7..a70362f 100644 --- a/hireq_test.go +++ b/greq_test.go @@ -10,12 +10,11 @@ import ( "github.com/gookit/goutil/dump" "github.com/gookit/goutil/fsutil" "github.com/gookit/goutil/netutil/httpreq" + "github.com/gookit/goutil/testutil" "github.com/gookit/goutil/testutil/assert" "github.com/gookit/greq" ) -var testBaseURL = "https://httpbin.org" - var testDoer = httpreq.DoerFunc(func(req *http.Request) (*http.Response, error) { tw := httptest.NewRecorder() dump.P("CORE++") @@ -33,6 +32,22 @@ var testDoer = httpreq.DoerFunc(func(req *http.Request) (*http.Response, error) return tw.Result(), err }) +var testBaseURL string + +func TestMain(m *testing.M) { + // create server + s := testutil.NewEchoServer() + defer s.Close() + testBaseURL = "http://" + s.Listener.Addr().String() + fmt.Println("Test server listen on:", testBaseURL) + + // with base url + greq.BaseURL(testBaseURL) + + // do something + m.Run() +} + func TestHReq_Doer(t *testing.T) { buf := &bytes.Buffer{} @@ -46,8 +61,9 @@ func TestHReq_Doer(t *testing.T) { resp, err := greq.New(testBaseURL). Doer(testDoer). Use(mid0). - UserAgent("custom-client/1.0"). - Do("/get", "GET") + UserAgent("custom-cli/1.0"). + Get("/get"). + Do() assert.NoErr(t, err) assert.True(t, resp.IsOK()) @@ -59,8 +75,8 @@ func TestHReq_Doer(t *testing.T) { func TestHReq_Send(t *testing.T) { resp, err := greq.New(testBaseURL). - UserAgent("custom-client/1.0"). - Send("/get") + UserAgent("custom-cli/1.0"). + Send("GET", "/get") assert.NoErr(t, err) assert.True(t, resp.IsOK()) @@ -75,7 +91,7 @@ func TestHReq_Send(t *testing.T) { headers := retMp["headers"].(map[string]any) assert.Contains(t, headers, "User-Agent") - assert.Eq(t, "custom-client/1.0", headers["User-Agent"]) + assert.Eq(t, "custom-cli/1.0", headers["User-Agent"]) } func TestHReq_GetDo(t *testing.T) { @@ -95,8 +111,9 @@ func TestHReq_GetDo(t *testing.T) { func TestHReq_PostDo(t *testing.T) { resp, err := greq.New(testBaseURL). + UserAgent(greq.AgentCURL). JSONType(). - PostDo("/post") + PostDo("/post", `{"name": "inhere"}`) assert.NoErr(t, err) assert.True(t, resp.IsOK()) @@ -110,11 +127,11 @@ func TestHReq_PostDo(t *testing.T) { func TestHReq_String(t *testing.T) { str := greq.New(testBaseURL). - UserAgent("some-client/1.0"). + UserAgent("some-cli/1.0"). BasicAuth("inhere", "some string"). JSONType(). - Body("hi, with body"). - Post("/post"). + StringBody("hi, with body"). + Post("/post", `{"name": "inhere"}`). String() fmt.Println(str) diff --git a/hireq.go b/hireq.go deleted file mode 100644 index e8b98b8..0000000 --- a/hireq.go +++ /dev/null @@ -1,685 +0,0 @@ -// Package greq is a simple http client request builder, inspired from https://github.com/dghubble/sling -package greq - -import ( - "bytes" - "context" - "io" - "net/http" - "net/url" - "os" - "strings" - - "github.com/gookit/goutil/fsutil" - "github.com/gookit/goutil/netutil/httpctype" - "github.com/gookit/goutil/netutil/httpreq" - "github.com/gookit/goutil/strutil" -) - -// DefaultDoer for request. -var DefaultDoer = http.DefaultClient - -// HandleFunc for the Middleware -type HandleFunc func(r *http.Request) (*Response, error) - -// RequestCreator interface -type RequestCreator interface { - NewRequest(method, target string, body io.Reader) *http.Request -} - -// RequestCreatorFunc func -type RequestCreatorFunc func(method, target string, body io.Reader) *http.Request - -// HiReq is an HTTP Request builder and sender. -type HiReq struct { - client httpreq.Doer - // core handler. - handler HandleFunc - middles []Middleware - // http method eg: GET,POST - method string - header http.Header - baseURL string - // pathURL only valid in one request - pathURL string - // query params data. allow: map[string]string, url.Values - queryParams url.Values - // body provider - bodyProvider BodyProvider - respDecoder RespDecoder - // BeforeSend callback - BeforeSend func(r *http.Request) -} - -// New create -func New(baseURL ...string) *HiReq { - h := &HiReq{ - client: http.DefaultClient, - method: http.MethodGet, - header: make(http.Header), - // default use JSON decoder - respDecoder: jsonDecoder{}, - queryParams: make(url.Values, 0), - } - - if len(baseURL) > 0 { - h.baseURL = baseURL[0] - } - return h -} - -// New create an instance from current. -func (h *HiReq) New() *HiReq { - // copy Headers pairs into new Header map - headerCopy := make(http.Header) - for k, v := range h.header { - headerCopy[k] = v - } - - return &HiReq{ - client: h.client, - method: h.method, - baseURL: h.baseURL, - header: headerCopy, - // queryParams: append([]any{}, s.queryParams...), - bodyProvider: h.bodyProvider, - respDecoder: h.respDecoder, - } -} - -// ------------ Config ------------ - -// Doer custom set http request doer. -// If a nil client is given, the DefaultDoer will be used. -func (h *HiReq) Doer(doer httpreq.Doer) *HiReq { - if doer != nil { - h.client = doer - } else { - h.client = DefaultDoer - } - - return h -} - -// Client custom set http request doer -func (h *HiReq) Client(doer httpreq.Doer) *HiReq { - return h.Doer(doer) -} - -// HttpClient custom set http client as request doer -func (h *HiReq) HttpClient(hClient *http.Client) *HiReq { - return h.Doer(hClient) -} - -// Config custom config http request doer -func (h *HiReq) Config(fn func(doer httpreq.Doer)) *HiReq { - fn(h.client) - return h -} - -// ConfigHClient custom config http client. -// -// Usage: -// -// h.ConfigHClient(func(hClient *http.Client) { -// hClient.Timeout = 30 * time.Second -// }) -func (h *HiReq) ConfigHClient(fn func(hClient *http.Client)) *HiReq { - if hc, ok := h.client.(*http.Client); ok { - fn(hc) - } else { - panic("the doer is not an *http.Client") - } - - return h -} - -// Use one or multi middlewares -func (h *HiReq) Use(middles ...Middleware) *HiReq { - return h.Middlewares(middles...) -} - -// Uses one or multi middlewares -func (h *HiReq) Uses(middles ...Middleware) *HiReq { - return h.Middlewares(middles...) -} - -// Middleware add one or multi middlewares -func (h *HiReq) Middleware(middles ...Middleware) *HiReq { - return h.Middlewares(middles...) -} - -// Middlewares add one or multi middlewares -func (h *HiReq) Middlewares(middles ...Middleware) *HiReq { - h.middles = append(h.middles, middles...) - return h -} - -// WithRespDecoder for client -func (h *HiReq) WithRespDecoder(respDecoder RespDecoder) *HiReq { - h.respDecoder = respDecoder - return h -} - -// OnBeforeSend for client -func (h *HiReq) OnBeforeSend(fn func(r *http.Request)) *HiReq { - h.BeforeSend = fn - return h -} - -// ------------ Method ------------ - -// Method set http method name. -func (h *HiReq) Method(method string) *HiReq { - h.method = method - return h -} - -// Head sets the method to HEAD and request the pathURL, then send request and return response. -func (h *HiReq) Head(pathURL string) *HiReq { - return h.Method(http.MethodHead).PathURL(pathURL) -} - -// HeadDo sets the method to HEAD and request the pathURL, -// then send request and return response. -func (h *HiReq) HeadDo(pathURL string) (*Response, error) { - return h.Send(pathURL, http.MethodHead) -} - -// Get sets the method to GET and sets the given pathURL -func (h *HiReq) Get(pathURL string) *HiReq { - return h.Method(http.MethodGet).PathURL(pathURL) -} - -// GetDo sets the method to GET and sets the given pathURL, -// then send request and return response. -func (h *HiReq) GetDo(pathURL string) (*Response, error) { - return h.Send(pathURL, http.MethodGet) -} - -// Post sets the method to POST and sets the given pathURL -func (h *HiReq) Post(pathURL string) *HiReq { - return h.Method(http.MethodPost).PathURL(pathURL) -} - -// PostDo sets the method to POST and sets the given pathURL, -// then send request and return http response. -func (h *HiReq) PostDo(pathURL string) (*Response, error) { - return h.Send(pathURL, http.MethodPost) -} - -// Put sets the method to PUT and sets the given pathURL -func (h *HiReq) Put(pathURL string) *HiReq { - return h.Method(http.MethodPut).PathURL(pathURL) -} - -// PutDo sets the method to PUT and sets the given pathURL, -// then send request and return http response. -func (h *HiReq) PutDo(pathURL string) (*Response, error) { - return h.Send(pathURL, http.MethodPut) -} - -// Patch sets the method to PATCH and sets the given pathURL -func (h *HiReq) Patch(pathURL string) *HiReq { - return h.Method(http.MethodPatch).PathURL(pathURL) -} - -// PatchDo sets the method to PATCH and sets the given pathURL, -// then send request and return http response. -func (h *HiReq) PatchDo(pathURL string) (*Response, error) { - return h.Send(pathURL, http.MethodPatch) -} - -// Delete sets the method to DELETE and sets the given pathURL -func (h *HiReq) Delete(pathURL string) *HiReq { - return h.Method(http.MethodDelete).PathURL(pathURL) -} - -// DeleteDo sets the method to DELETE and sets the given pathURL, -// then send request and return http response. -func (h *HiReq) DeleteDo(pathURL string) (*Response, error) { - return h.Send(pathURL, http.MethodDelete) -} - -// Trace sets the method to TRACE and sets the given pathURL, -// then send request and return http response. -func (h *HiReq) Trace(pathURL string) *HiReq { - return h.Method(http.MethodTrace).PathURL(pathURL) -} - -// TraceDo sets the method to TRACE and sets the given pathURL, -// then send request and return http response. -func (h *HiReq) TraceDo(pathURL string) (*Response, error) { - return h.Send(pathURL, http.MethodTrace) -} - -// Options sets the method to OPTIONS and request the pathURL, -// then send request and return response. -func (h *HiReq) Options(pathURL string) *HiReq { - return h.Method(http.MethodOptions).PathURL(pathURL) -} - -// OptionsDo sets the method to OPTIONS and request the pathURL, -// then send request and return response. -func (h *HiReq) OptionsDo(pathURL string) (*Response, error) { - return h.Send(pathURL, http.MethodOptions) -} - -// Connect sets the method to CONNECT and sets the given pathURL, -// then send request and return http response. -func (h *HiReq) Connect(pathURL string) (*Response, error) { - return h.Send(pathURL, http.MethodConnect) -} - -// ConnectDo sets the method to CONNECT and sets the given pathURL, -// then send request and return http response. -func (h *HiReq) ConnectDo(pathURL string) (*Response, error) { - return h.Send(pathURL, http.MethodConnect) -} - -// ----------- URL, query params ------------ - -// BaseURL set base URL for all request -func (h *HiReq) BaseURL(baseURL string) *HiReq { - h.baseURL = baseURL - return h -} - -// PathURL set path URL for current request -func (h *HiReq) PathURL(pathURL string) *HiReq { - h.pathURL = pathURL - return h -} - -// QueryParam appends new k-v param to the query string. -func (h *HiReq) QueryParam(key string, value any) *HiReq { - h.queryParams.Add(key, strutil.MustString(value)) - return h -} - -// QueryParams appends url.Values/map[string]string to the query string. -// The value will be encoded as url query parameters on send requests (see Send()). -func (h *HiReq) QueryParams(ps any) *HiReq { - if ps != nil { - queryValues := httpreq.ToQueryValues(ps) - - for key, values := range queryValues { - for _, value := range values { - h.queryParams.Add(key, value) - } - } - } - - return h -} - -// QueryValues appends url.Values to the query string. -// The value will be encoded as url query parameters on new requests (see Send()). -func (h *HiReq) QueryValues(values url.Values) *HiReq { - return h.QueryParams(values) -} - -// ----------- Header ------------ - -// AddHeader adds the key, value pair in Headers, appending values for existing keys -// to the key's values. Header keys are canonicalized. -func (h *HiReq) AddHeader(key, value string) *HiReq { - h.header.Add(key, value) - return h -} - -// SetHeader sets the key, value pair in Headers, replacing existing values -// associated with key. Header keys are canonicalized. -func (h *HiReq) SetHeader(key, value string) *HiReq { - h.header.Set(key, value) - return h -} - -// AddHeaders adds all the http.Header values, appending values for existing keys -// to the key's values. Header keys are canonicalized. -func (h *HiReq) AddHeaders(headers http.Header) *HiReq { - for key, values := range headers { - for i := range values { - h.header.Add(key, values[i]) - } - } - return h -} - -// SetHeaders sets all the http.Header values, replacing values for existing keys -// to the key's values. Header keys are canonicalized. -func (h *HiReq) SetHeaders(headers http.Header) *HiReq { - for key, values := range headers { - for i := range values { - if i == 0 { - h.header.Set(key, values[i]) - } else { - h.header.Add(key, values[i]) - } - } - } - return h -} - -// ContentType with custom ContentType header -// -// Usage: -// -// // json type -// h.ContentType(httpctype.JSON) -// // form type -// h.ContentType(httpctype.Form) -func (h *HiReq) ContentType(value string) *HiReq { - return h.SetHeader(httpctype.Key, value) -} - -// XMLType with xml Content-Type header -func (h *HiReq) XMLType() *HiReq { - return h.SetHeader(httpctype.Key, httpctype.XML) -} - -// JSONType with json Content-Type header -func (h *HiReq) JSONType() *HiReq { - return h.SetHeader(httpctype.Key, httpctype.JSON) -} - -// FormType with from Content-Type header -func (h *HiReq) FormType() *HiReq { - return h.SetHeader(httpctype.Key, httpctype.Form) -} - -// MultipartType with multipart/form-data Content-Type header -func (h *HiReq) MultipartType() *HiReq { - return h.SetHeader(httpctype.Key, httpctype.FormData) -} - -// UserAgent with User-Agent header setting. -func (h *HiReq) UserAgent(value string) *HiReq { - return h.SetHeader("User-Agent", value) -} - -// UserAuth with user auth header value. -func (h *HiReq) UserAuth(value string) *HiReq { - return h.SetHeader("Authorization", value) -} - -// BasicAuth sets the Authorization header to use HTTP Basic Authentication -// with the provided username and password. With HTTP Basic Authentication -// the provided username and password are not encrypted. -func (h *HiReq) BasicAuth(username, password string) *HiReq { - return h.SetHeader("Authorization", httpreq.BuildBasicAuth(username, password)) -} - -// SetCookies to request -func (h *HiReq) SetCookies(hcs ...*http.Cookie) *HiReq { - var b strings.Builder - for i, hc := range hcs { - if i > 0 { - b.WriteByte(';') - } - b.WriteString(hc.String()) - } - - return h.SetCookieString(b.String()) -} - -// SetCookieString set cookie header value. -// -// Usage: -// -// h.New(). -// SetCookieString("name=inhere;age=30"). -// GetDo("/some/api") -func (h *HiReq) SetCookieString(value string) *HiReq { - // return h.AddHeader("Set-Cookie", value) - return h.AddHeader("Cookie", value) -} - -// ----------- Request Body ------------ - -// Body with custom any type body -func (h *HiReq) Body(body any) *HiReq { - return h.AnyBody(body) -} - -// AnyBody with custom any type body -func (h *HiReq) AnyBody(body any) *HiReq { - switch typVal := body.(type) { - case io.Reader: - h.BodyReader(typVal) - case BodyProvider: - h.BodyProvider(typVal) - case string: - h.StringBody(typVal) - case []byte: - h.BytesBody(typVal) - default: - panic("invalid data type as body") - } - return h -} - -// BodyReader with custom io reader body -func (h *HiReq) BodyReader(r io.Reader) *HiReq { - h.bodyProvider = bodyProvider{body: r} - return h -} - -// BodyProvider with custom body provider -func (h *HiReq) BodyProvider(bp BodyProvider) *HiReq { - h.bodyProvider = bp - return h -} - -// FileContentsBody read file contents as body -func (h *HiReq) FileContentsBody(filePath string) *HiReq { - file, err := os.OpenFile(filePath, os.O_RDONLY, fsutil.DefaultFilePerm) - if err != nil { - panic(err) - } - return h.BodyReader(file) -} - -// JSONBody with JSON data body -func (h *HiReq) JSONBody(jsonData any) *HiReq { - h.bodyProvider = jsonBodyProvider{ - payload: jsonData, - } - return h -} - -// FormBody with form data body -func (h *HiReq) FormBody(formData any) *HiReq { - h.bodyProvider = formBodyProvider{ - payload: formData, - } - return h -} - -// BytesBody with custom string body -func (h *HiReq) BytesBody(bs []byte) *HiReq { - return h.BodyReader(bytes.NewReader(bs)) -} - -// StringBody with custom string body -func (h *HiReq) StringBody(s string) *HiReq { - return h.BodyReader(strings.NewReader(s)) -} - -// Multipart with custom multipart body -func (h *HiReq) Multipart(key, value string) *HiReq { - // TODO - return h -} - -// ----------- Do send request ------------ - -// Do send request and return response -func (h *HiReq) Do(pathURLAndMethod ...string) (*Response, error) { - return h.SendWithCtx(context.Background(), pathURLAndMethod...) -} - -// Send request and return response -func (h *HiReq) Send(pathURLAndMethod ...string) (*Response, error) { - return h.SendWithCtx(context.Background(), pathURLAndMethod...) -} - -// MustSend send request and return response, will panic on error -func (h *HiReq) MustSend(pathURLAndMethod ...string) *Response { - resp, err := h.SendWithCtx(context.Background(), pathURLAndMethod...) - if err != nil { - panic(err) - } - - return resp -} - -// SendRaw http request text. -func (h *HiReq) SendRaw(raw string) (*Response, error) { - method := "GET" - reqUrl := "TODO" - - var body io.Reader - - req, err := http.NewRequest(method, reqUrl, body) - if err != nil { - return nil, err - } - - return h.SendRequest(req) -} - -// ReqOption type -type ReqOption = httpreq.ReqOption - -// SendWithOpt send request with option, then return response -func (h *HiReq) SendWithOpt(pathURL string, opt *ReqOption) (*Response, error) { - // ensure option - opt = ensureOpt(opt) - - // build request - req, err := h.NewRequestWithCtx(opt.Context, pathURL, opt.Method) - if err != nil { - return nil, err - } - - // set headers - if len(opt.HeaderMap) > 0 { - httpreq.AddHeaderMap(req, opt.HeaderMap) - } - if len(opt.ContentType) > 0 { - req.Header.Set("Content-Type", opt.ContentType) - } - - // do send - return h.SendRequest(req) -} - -// DoWithCtx request with context, then return response -func (h *HiReq) DoWithCtx(ctx context.Context, pathURLAndMethod ...string) (*Response, error) { - return h.SendWithCtx(ctx, pathURLAndMethod...) -} - -// SendWithCtx request with context, then return response -func (h *HiReq) SendWithCtx(ctx context.Context, pathURLAndMethod ...string) (*Response, error) { - req, err := h.NewRequestWithCtx(ctx, pathURLAndMethod...) - if err != nil { - return nil, err - } - - // do send - return h.SendRequest(req) -} - -// SendRequest send request -func (h *HiReq) SendRequest(req *http.Request) (*Response, error) { - // call before send. - if h.BeforeSend != nil { - h.BeforeSend(req) - } - - // wrap middlewares - h.wrapMiddlewares() - - // do send - return h.handler(req) -} - -// ----------- Build request ------------ - -// Build new request -func (h *HiReq) Build(pathURLAndMethod ...string) (*http.Request, error) { - return h.NewRequestWithCtx(context.Background(), pathURLAndMethod...) -} - -// NewRequest build new request -func (h *HiReq) NewRequest(pathURLAndMethod ...string) (*http.Request, error) { - return h.NewRequestWithCtx(context.Background(), pathURLAndMethod...) -} - -// NewRequestWithCtx build new request with context -func (h *HiReq) NewRequestWithCtx(ctx context.Context, pathURLAndMethod ...string) (*http.Request, error) { - method := h.method - pathURL := h.pathURL - if ln := len(pathURLAndMethod); ln > 0 { - pathURL = pathURLAndMethod[0] - if ln > 1 && len(pathURLAndMethod[1]) > 0 { - method = pathURLAndMethod[1] - } - } - - fullURL := pathURL - if len(h.baseURL) > 0 { - if !strings.HasPrefix(pathURL, "http") { - fullURL = h.baseURL + pathURL - } else if len(pathURL) == 0 { - fullURL = h.baseURL - } - } - - reqURL, err := url.Parse(fullURL) - if err != nil { - return nil, err - } - if reqURL.Scheme == "" { - reqURL.Scheme = "https" - } - - // append query params - if err = httpreq.AppendQueryToURL(reqURL, h.queryParams); err != nil { - return nil, err - } - - var body io.Reader - if h.bodyProvider != nil { - body, err = h.bodyProvider.Body() - if err != nil { - return nil, err - } - - if cType := h.bodyProvider.ContentType(); cType != "" { - h.ContentType(cType) - } - } - - req, err := http.NewRequestWithContext(ctx, method, reqURL.String(), body) - if err != nil { - return nil, err - } - - // copy headers - httpreq.AddHeaders(req, h.header) - - // reset after request build. - h.pathURL = "" - return req, err -} - -// String request to string. -func (h *HiReq) String() string { - r, err := h.Build() - if err != nil { - return "" - } - return httpreq.RequestToString(r) -} diff --git a/middle.go b/middle.go index ebadcc3..75d3cc7 100644 --- a/middle.go +++ b/middle.go @@ -2,7 +2,7 @@ package greq import "net/http" -// Middleware interface for client request. +// Middleware interface for cli request. type Middleware interface { Handle(r *http.Request, next HandleFunc) (*Response, error) } @@ -15,9 +15,9 @@ func (mf MiddleFunc) Handle(r *http.Request, next HandleFunc) (*Response, error) return mf(r, next) } -func (h *HiReq) wrapMiddlewares() { +func (h *Client) wrapMiddlewares() { h.handler = func(r *http.Request) (*Response, error) { - rawResp, err := h.client.Do(r) + rawResp, err := h.doer.Do(r) if err != nil { return nil, err } @@ -34,7 +34,7 @@ func (h *HiReq) wrapMiddlewares() { } } -func (h *HiReq) wrapMiddleware(m Middleware) { +func (h *Client) wrapMiddleware(m Middleware) { next := h.handler // wrap handler diff --git a/option.go b/option.go new file mode 100644 index 0000000..8996712 --- /dev/null +++ b/option.go @@ -0,0 +1,109 @@ +package greq + +import ( + "context" + "net/http" + gourl "net/url" + "time" + + "github.com/gookit/goutil/netutil/httpreq" +) + +// Options for a request build +type Options struct { + // url or path for current request + pathURL string + + // Method for request + Method string + // ContentType header + ContentType string + // Headers for request + Header http.Header + // HeaderM map string data. + HeaderM map[string]string + + // Query params data. + Query gourl.Values + QueryM map[string]any + + // Data for request, will be encoded to query string or req body. + // + // type allow: string, []byte, io.Reader, io.ReadCloser, url.Values, map[string]string + Data any + // Body data for request, only for POST, PUT, PATCH + // + // type allow: string, []byte, io.Reader, io.ReadCloser, url.Values, map[string]string + Body any + // Provider body data provider, can with custom content-type + Provider BodyProvider + + // EncodeJSON req body + EncodeJSON bool + // Timeout unit: ms + Timeout int + // TCancelFn will auto set it on Timeout > 0 + TCancelFn context.CancelFunc + // Context for request + Context context.Context + // Logger for request + Logger httpreq.ReqLogger +} + +// OptionFn for config request options +type OptionFn func(opt *Options) + +// NewOpt for request +func NewOpt(fns ...OptionFn) *Options { + return NewOpt2(fns, "") +} + +// NewOpt2 for request +func NewOpt2(fns []OptionFn, method string) *Options { + opt := &Options{ + Method: method, + Header: make(http.Header), + HeaderM: make(map[string]string), + Query: make(gourl.Values), + } + for _, fn := range fns { + fn(opt) + } + return opt +} + +func orCreate(opt *Options) *Options { + if opt == nil { + opt = &Options{} + } + return opt +} + +func ensureOpt(opt *Options) *Options { + if opt == nil { + opt = &Options{} + } + if opt.Context == nil { + opt.Context = context.Background() + } + + if opt.Timeout > 0 && opt.TCancelFn == nil { + opt.Context, opt.TCancelFn = context.WithTimeout( + opt.Context, + time.Duration(opt.Timeout)*time.Millisecond, + ) + } + return opt +} + +// WithMethod set method +func (opt *Options) WithMethod(method string) *Options { + if method != "" { + opt.Method = method + } + return opt +} + +// +// ----------- Build Request ------------ +// diff --git a/req_body.go b/req_body.go index 3a3c978..1086580 100644 --- a/req_body.go +++ b/req_body.go @@ -81,6 +81,5 @@ func (p formBodyProvider) Body() (io.Reader, error) { if str, ok := p.payload.(string); ok { return strings.NewReader(str), nil } - - return nil, errors.New("invalid payload data for form body") + return nil, errors.New("invalid payload data for Form body") } diff --git a/resp.go b/resp.go index c7d88ac..5d7ee2c 100644 --- a/resp.go +++ b/resp.go @@ -13,7 +13,7 @@ import ( // Response is a http.Response wrapper type Response struct { *http.Response - // decoder for response, default will extends from HiReq.respDecoder + // decoder for response, default will extends from Client.respDecoder decoder RespDecoder } diff --git a/resp_test.go b/resp_test.go index 7ef4d19..439ac51 100644 --- a/resp_test.go +++ b/resp_test.go @@ -10,7 +10,7 @@ import ( func TestResponse_String(t *testing.T) { resp, err := greq.New(testBaseURL). - UserAgent("custom-client/1.0"). + UserAgent("custom-cli/1.0"). GetDo("/get") assert.NoErr(t, err) diff --git a/std.go b/std.go index de9e763..8c7a7cf 100644 --- a/std.go +++ b/std.go @@ -11,109 +11,95 @@ import ( var std = New() // Std instance -func Std() *HiReq { +func Std() *Client { return std } -// Reset std -func Reset() *HiReq { +// Reset std default settings +func Reset() *Client { std.header = make(http.Header) - - std.bodyProvider = nil - std.queryParams = make(url.Values, 0) - return std -} - -func reset() *HiReq { - std.bodyProvider = nil + std.query = make(url.Values, 0) return std } // BaseURL set base URL for request -func BaseURL(baseURL string) *HiReq { +func BaseURL(baseURL string) *Client { return std.BaseURL(baseURL) } -// MustDo sets the method to POST and sets the given pathURL, -// then send request and return http response. -func MustDo(method, pathURL string, body ...any) *Response { - if len(body) > 0 { - std.Body(body[0]) - } else { - reset() - } - - resp, err := std.Send(pathURL, method) - goutil.PanicErr(err) - return resp -} - // GetDo sets the method to GET and sets the given pathURL, then send request and return response. -func GetDo(pathURL string) (*Response, error) { - return reset().Send(pathURL, http.MethodGet) +func GetDo(pathURL string, optFns ...OptionFn) (*Response, error) { + return std.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodGet)) } // PostDo sets the method to POST and sets the given pathURL, // then send request and return http response. -func PostDo(pathURL string, body ...any) (*Response, error) { - if len(body) > 0 { - std.Body(body[0]) - } else { - reset() - } +func PostDo(pathURL string, data any, optFns ...OptionFn) (*Response, error) { + opt := NewOpt2(optFns, http.MethodPost) + opt.Body = data - return std.Send(pathURL, http.MethodPost) + return std.SendWithOpt(pathURL, opt) } // PutDo sets the method to PUT and sets the given pathURL, // then send request and return http response. -func PutDo(pathURL string, body ...any) (*Response, error) { - if len(body) > 0 { - std.Body(body[0]) - } else { - reset() - } +func PutDo(pathURL string, data any, optFns ...OptionFn) (*Response, error) { + opt := NewOpt2(optFns, http.MethodPut) + opt.Body = data - return std.Send(pathURL, http.MethodPut) + return std.SendWithOpt(pathURL, opt) } // PatchDo sets the method to PATCH and sets the given pathURL, // then send request and return http response. -func PatchDo(pathURL string, body ...any) (*Response, error) { - if len(body) > 0 { - std.Body(body[0]) - } else { - reset() - } +func PatchDo(pathURL string, data any, optFns ...OptionFn) (*Response, error) { + opt := NewOpt2(optFns, http.MethodPatch) + opt.Body = data - return std.Send(pathURL, http.MethodPatch) + return std.SendWithOpt(pathURL, opt) } // DeleteDo sets the method to DELETE and sets the given pathURL, // then send request and return http response. -func DeleteDo(pathURL string) (*Response, error) { - return reset().Send(pathURL, http.MethodDelete) +func DeleteDo(pathURL string, optFns ...OptionFn) (*Response, error) { + return std.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodDelete)) } -// Head sets the method to HEAD and request the pathURL, then send request and return response. -func Head(pathURL string) (*Response, error) { - return reset().Send(pathURL, http.MethodHead) +// HeadDo sets the method to HEAD and request the pathURL, then send request and return response. +func HeadDo(pathURL string, optFns ...OptionFn) (*Response, error) { + return std.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodHead)) } // TraceDo sets the method to TRACE and sets the given pathURL, // then send request and return http response. -func TraceDo(pathURL string) (*Response, error) { - return reset().Send(pathURL, http.MethodTrace) +func TraceDo(pathURL string, optFns ...OptionFn) (*Response, error) { + return std.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodTrace)) } // OptionsDo sets the method to OPTIONS and request the pathURL, // then send request and return response. -func OptionsDo(pathURL string) (*Response, error) { - return reset().Send(pathURL, http.MethodOptions) +func OptionsDo(pathURL string, optFns ...OptionFn) (*Response, error) { + return std.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodOptions)) } // ConnectDo sets the method to CONNECT and sets the given pathURL, // then send request and return http response. -func ConnectDo(pathURL string) (*Response, error) { - return reset().Send(pathURL, http.MethodConnect) +func ConnectDo(pathURL string, optFns ...OptionFn) (*Response, error) { + return std.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodConnect)) +} + +// SendDo sets the method to POST and sets the given pathURL, +// then send request and return http response. +func SendDo(method, pathURL string, optFns ...OptionFn) (*Response, error) { + return std.SendWithOpt(pathURL, NewOpt2(optFns, method)) +} + +// MustDo sets the method to POST and sets the given pathURL, +// then send request and return http response. +func MustDo(method, pathURL string, optFns ...OptionFn) *Response { + resp, err := std.SendWithOpt(pathURL, NewOpt2(optFns, method)) + if err != nil { + goutil.Panicf("send request error: %s", err.Error()) + } + return resp } diff --git a/std_test.go b/std_test.go index 2c5eec4..4020d87 100644 --- a/std_test.go +++ b/std_test.go @@ -5,25 +5,76 @@ import ( "testing" "github.com/gookit/goutil/dump" + "github.com/gookit/goutil/testutil" "github.com/gookit/goutil/testutil/assert" "github.com/gookit/greq" ) -func init() { - greq.BaseURL(testBaseURL) -} - -func TestGetDo(t *testing.T) { - resp, err := greq.GetDo("/get") - - assert.NoErr(t, err) - assert.True(t, resp.IsOK()) - assert.True(t, resp.IsSuccessful()) - - retMp := make(map[string]any) - err = resp.Decode(&retMp) - assert.NoErr(t, err) - dump.P(retMp) +func TestREST_methodDo(t *testing.T) { + t.Run("GET", func(t *testing.T) { + resp, err := greq.GetDo("/get") + + assert.NoErr(t, err) + assert.True(t, resp.IsOK()) + assert.True(t, resp.IsSuccessful()) + + retMp := make(map[string]any) + err = resp.Decode(&retMp) + assert.NoErr(t, err) + dump.P(retMp) + }) + + t.Run("POST", func(t *testing.T) { + resp, err := greq.PostDo("/post", `{"name": "inhere"}`) + + assert.NoErr(t, err) + assert.True(t, resp.IsOK()) + assert.True(t, resp.IsSuccessful()) + + retMp := make(map[string]any) + err = resp.Decode(&retMp) + assert.NoErr(t, err) + dump.P(retMp) + }) + + t.Run("PUT", func(t *testing.T) { + resp, err := greq.PutDo(testBaseURL+"/put", `{"name": "inhere"}`) + + assert.NoErr(t, err) + assert.True(t, resp.IsOK()) + assert.True(t, resp.IsSuccessful()) + + retMp := make(map[string]any) + err = resp.Decode(&retMp) + assert.NoErr(t, err) + dump.P(retMp) + }) + + t.Run("PATCH", func(t *testing.T) { + resp, err := greq.PatchDo(testBaseURL+"/patch", "hello") + + assert.NoErr(t, err) + assert.True(t, resp.IsOK()) + assert.True(t, resp.IsSuccessful()) + + rr := &testutil.EchoReply{} + err = resp.Decode(&rr) + assert.NoErr(t, err) + dump.P(rr) + }) + + t.Run("DELETE", func(t *testing.T) { + resp, err := greq.DeleteDo(testBaseURL + "/delete") + + assert.NoErr(t, err) + assert.True(t, resp.IsOK()) + assert.True(t, resp.IsSuccessful()) + + retMp := make(map[string]any) + err = resp.Decode(&retMp) + assert.NoErr(t, err) + dump.P(retMp) + }) } func TestGetDo_with_QueryParams(t *testing.T) { @@ -45,94 +96,44 @@ func TestGetDo_with_QueryParams(t *testing.T) { dump.P(retMp) } -func TestPostDo(t *testing.T) { - resp, err := greq.PostDo("/post") - - assert.NoErr(t, err) - assert.True(t, resp.IsOK()) - assert.True(t, resp.IsSuccessful()) - - retMp := make(map[string]any) - err = resp.Decode(&retMp) - assert.NoErr(t, err) - dump.P(retMp) -} - -func TestPutDo(t *testing.T) { - resp, err := greq.PutDo("/put") - - assert.NoErr(t, err) - assert.True(t, resp.IsOK()) - assert.True(t, resp.IsSuccessful()) - - retMp := make(map[string]any) - err = resp.Decode(&retMp) - assert.NoErr(t, err) - dump.P(retMp) -} - -func TestPatchDo(t *testing.T) { - resp, err := greq.PatchDo("/patch") +func TestOther_methodDo(t *testing.T) { + t.Run("Head", func(t *testing.T) { + resp, err := greq.Reset().HeadDo(testBaseURL + "/") + fmt.Println(resp.String()) - assert.NoErr(t, err) - assert.True(t, resp.IsOK()) - assert.True(t, resp.IsSuccessful()) + assert.NoErr(t, err) + assert.True(t, resp.IsOK()) + assert.True(t, resp.IsSuccessful()) + assert.False(t, resp.IsEmptyBody()) - retMp := make(map[string]any) - err = resp.Decode(&retMp) - assert.NoErr(t, err) - dump.P(retMp) -} + assert.Empty(t, resp.BodyString()) + }) -func TestDeleteDo(t *testing.T) { - resp, err := greq.DeleteDo("/delete") + t.Run("Options", func(t *testing.T) { + resp, err := greq.OptionsDo(testBaseURL + "/") + fmt.Println(resp.String()) - assert.NoErr(t, err) - assert.True(t, resp.IsOK()) - assert.True(t, resp.IsSuccessful()) + assert.NoErr(t, err) + assert.True(t, resp.IsOK()) + assert.True(t, resp.IsSuccessful()) + assert.False(t, resp.IsEmptyBody()) - retMp := make(map[string]any) - err = resp.Decode(&retMp) - assert.NoErr(t, err) - dump.P(retMp) -} + assert.Empty(t, resp.BodyString()) + assert.NotEmpty(t, resp.HeaderString()) + }) -func TestHeadDo(t *testing.T) { - resp, err := greq.Reset().HeadDo("/") - fmt.Println(resp.String()) + t.Run("Trace", func(t *testing.T) { + resp, err := greq.TraceDo(testBaseURL + "/") + fmt.Println(resp.String()) - assert.NoErr(t, err) - assert.True(t, resp.IsOK()) - assert.True(t, resp.IsSuccessful()) - assert.False(t, resp.IsEmptyBody()) - - assert.Empty(t, resp.BodyString()) -} - -func TestOptionsDo(t *testing.T) { - resp, err := greq.OptionsDo("/") - fmt.Println(resp.String()) - - assert.NoErr(t, err) - assert.True(t, resp.IsOK()) - assert.True(t, resp.IsEmptyBody()) - assert.True(t, resp.IsSuccessful()) - - assert.Empty(t, resp.BodyString()) - assert.NotEmpty(t, resp.HeaderString()) -} - -func TestTraceDo(t *testing.T) { - resp, err := greq.TraceDo("/") - fmt.Println(resp.String()) + assert.NoErr(t, err) + // assert.True(t, resp.IsNoBody()) + }) - assert.NoErr(t, err) - // assert.True(t, resp.IsNoBody()) -} + t.Run("Connect", func(t *testing.T) { + resp, err := greq.ConnectDo(testBaseURL + "/") + fmt.Println(resp.String()) -func TestConnectDo(t *testing.T) { - resp, err := greq.ConnectDo("/") - fmt.Println(resp.String()) - - assert.NoErr(t, err) + assert.NoErr(t, err) + }) }