Skip to content

Commit

Permalink
feat: add Context option and Stats (#34)
Browse files Browse the repository at this point in the history
* feat: add Context option

* refactor: encapsulate HTTP method

* feat: add Stats to Request

* feat: add traceInterceptor with httptrace
  • Loading branch information
wenchy authored Jun 3, 2024
1 parent 6a89a49 commit 9b50c14
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 138 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ An elegant and simple HTTP client package, which learned a lot from the well-kno
- [x] Connection Timeouts
- [ ] Chunked Requests
- [ ] .netrc Support
- [x] `context.Context` Support
- [x] Interceptor Support

## Examples

Expand Down
3 changes: 3 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ func (c *Client) do(ctx context.Context, r *Request) (*Response, error) {
}
*r.opts.DumpRequestOut = string(reqDump)
}
if ctx != nil {
r = r.WithContext(ctx)
}
// If the returned error is nil, the Response will contain
// a non-nil Body which the user is expected to close.
resp, err := c.Client.Do(r.Request)
Expand Down
40 changes: 40 additions & 0 deletions method.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package requests

import "net/http"

// Get sends an HTTP request with GET method.
//
// On error, any Response can be ignored. A non-nil Response with a
// non-nil error only occurs when Response.StatusCode() is not 2xx.
func Get(url string, options ...Option) (*Response, error) {
return callMethod(http.MethodGet, url, options...)
}

// Post sends an HTTP POST request.
func Post(url string, options ...Option) (*Response, error) {
return callMethod(http.MethodPost, url, options...)
}

// Put sends an HTTP request with PUT method.
//
// On error, any Response can be ignored. A non-nil Response with a
// non-nil error only occurs when Response.StatusCode() is not 2xx.
func Put(url string, options ...Option) (*Response, error) {
return callMethod(http.MethodPut, url, options...)
}

// Patch sends an HTTP request with PATCH method.
//
// On error, any Response can be ignored. A non-nil Response with a
// non-nil error only occurs when Response.StatusCode() is not 2xx.
func Patch(url string, options ...Option) (*Response, error) {
return callMethod(http.MethodPatch, url, options...)
}

// Delete sends an HTTP request with DELETE method.
//
// On error, any Response can be ignored. A non-nil Response with a
// non-nil error only occurs when Response.StatusCode() is not 2xx.
func Delete(url string, options ...Option) (*Response, error) {
return callMethod(http.MethodDelete, url, options...)
}
52 changes: 33 additions & 19 deletions options.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package requests

import (
"context"
"fmt"
"io"
"os"
Expand All @@ -9,8 +10,10 @@ import (
"github.com/Wenchy/requests/internal/auth"
)

// httpOptions defines all optional parameters for HTTP request.
type httpOptions struct {
// Options defines all optional parameters for HTTP request.
type Options struct {
ctx context.Context

Headers map[string]string
Params map[string]string
// body
Expand All @@ -36,11 +39,22 @@ type httpOptions struct {
}

// Option is the functional option type.
type Option func(*httpOptions)
type Option func(*Options)

// Context sets the HTTP request context.
//
// For outgoing client request, the context controls the entire lifetime of
// a request and its response: obtaining a connection, sending the request,
// and reading the response headers and body.
func Context(ctx context.Context) Option {
return func(opts *Options) {
opts.ctx = ctx
}
}

// Headers sets the HTTP headers.
func Headers(headers map[string]string) Option {
return func(opts *httpOptions) {
return func(opts *Options) {
if opts.Headers != nil {
for k, v := range headers {
opts.Headers[k] = v
Expand Down Expand Up @@ -71,7 +85,7 @@ func HeaderPairs(kv ...string) Option {

// Params sets the given params into the URL querystring.
func Params(params map[string]string) Option {
return func(opts *httpOptions) {
return func(opts *Options) {
if opts.Params != nil {
for k, v := range params {
opts.Params[k] = v
Expand Down Expand Up @@ -102,22 +116,22 @@ func ParamPairs(kv ...string) Option {

// Body sets io.Reader to hold request body.
func Body(body io.Reader) Option {
return func(opts *httpOptions) {
return func(opts *Options) {
opts.Body = body
}
}

// Data sets raw string into the request body.
func Data(data any) Option {
return func(opts *httpOptions) {
return func(opts *Options) {
opts.Data = data
}
}

// Form sets the given form into the request body.
// It also sets the Content-Type as "application/x-www-form-urlencoded".
func Form(form map[string]string) Option {
return func(opts *httpOptions) {
return func(opts *Options) {
opts.Form = form
}
}
Expand All @@ -143,28 +157,28 @@ func FormPairs(kv ...string) Option {
// JSON marshals the given struct as JSON into the request body.
// It also sets the Content-Type as "application/json".
func JSON(v any) Option {
return func(opts *httpOptions) {
return func(opts *Options) {
opts.JSON = v
}
}

// ToText unmarshals HTTP response body to string.
func ToText(v *string) Option {
return func(opts *httpOptions) {
return func(opts *Options) {
opts.ToText = v
}
}

// ToJSON unmarshals HTTP response body to given struct as JSON.
func ToJSON(v any) Option {
return func(opts *httpOptions) {
return func(opts *Options) {
opts.ToJSON = v
}
}

// BasicAuth is the option to implement HTTP Basic Auth.
func BasicAuth(username, password string) Option {
return func(opts *httpOptions) {
return func(opts *Options) {
opts.AuthInfo = &auth.AuthInfo{
Type: auth.BasicAuth,
Username: username,
Expand All @@ -176,7 +190,7 @@ func BasicAuth(username, password string) Option {
// Files sets files to a map of (field, fileHandler).
// It also sets the Content-Type as "multipart/form-data".
func Files(files map[string]*os.File) Option {
return func(opts *httpOptions) {
return func(opts *Options) {
if opts.Files != nil {
for k, v := range files {
opts.Files[k] = v
Expand All @@ -195,7 +209,7 @@ func Files(files map[string]*os.File) Option {
//
// A Timeout of zero means no timeout. Default is 60s.
func Timeout(timeout time.Duration) Option {
return func(opts *httpOptions) {
return func(opts *Options) {
opts.Timeout = timeout
}
}
Expand All @@ -205,7 +219,7 @@ func Timeout(timeout time.Duration) Option {
//
// This is unrelated to the similarly named TCP keep-alives.
func DisableKeepAlives() Option {
return func(opts *httpOptions) {
return func(opts *Options) {
opts.DisableKeepAlives = true
}
}
Expand All @@ -217,15 +231,15 @@ func DisableKeepAlives() Option {
// - https://pkg.go.dev/net/http/httputil#DumpRequestOut
// - https://pkg.go.dev/net/http/httputil#DumpResponse
func Dump(req, resp *string) Option {
return func(opts *httpOptions) {
return func(opts *Options) {
opts.DumpRequestOut = req
opts.DumpResponse = resp
}
}

// newDefaultOptions creates a new default HTTP options.
func newDefaultOptions() *httpOptions {
return &httpOptions{
func newDefaultOptions() *Options {
return &Options{
Headers: map[string]string{},
Params: map[string]string{},
Form: nil,
Expand All @@ -234,7 +248,7 @@ func newDefaultOptions() *httpOptions {
}
}

func parseOptions(options ...Option) *httpOptions {
func parseOptions(options ...Option) *Options {
opts := newDefaultOptions()
for _, setter := range options {
setter(opts)
Expand Down
Loading

0 comments on commit 9b50c14

Please sign in to comment.