From 96efd03a78bc5830056634323faa715028e01bca Mon Sep 17 00:00:00 2001 From: vladimirvivien Date: Sun, 24 Nov 2024 20:16:15 -0500 Subject: [PATCH] Add context awareness to http functions --- functions.go | 14 ++++++- http.go | 23 +++++++++--- http/http_reader.go | 92 +++++++++++++++++++++++++++++++++++++++------ http/http_writer.go | 38 +++++++++++++++---- 4 files changed, 139 insertions(+), 28 deletions(-) diff --git a/functions.go b/functions.go index 649fac3..a8616fa 100644 --- a/functions.go +++ b/functions.go @@ -194,9 +194,14 @@ func FileWrite(path string) *fs.FileWriter { return DefaultEcho.FileWriteWithContext(context.Background(), path) } +// HttpGetWithContext uses context ctx to start an HTTP GET operation to retrieve resource at URL/path +func HttpGetWithContext(ctx context.Context, url string, paths ...string) *http.ResourceReader { + return DefaultEcho.HttpGetWithContext(ctx, url, paths...) +} + // HttpGet starts an HTTP GET operation to retrieve resource at URL/path func HttpGet(url string, paths ...string) *http.ResourceReader { - return DefaultEcho.HttpGet(url, paths...) + return DefaultEcho.HttpGetWithContext(context.Background(), url, paths...) } // Get is a convenient alias for HttpGet that retrieves specified resource at given URL/path @@ -204,9 +209,14 @@ func Get(url string, paths ...string) *http.Response { return DefaultEcho.Get(url, paths...) } +// HttpPostWithContext uses context ctx to start an HTTP POST operation to post resource to URL/path +func HttpPostWithContext(ctx context.Context, url string, paths ...string) *http.ResourceWriter { + return DefaultEcho.HttpPostWithContext(ctx, url, paths...) +} + // HttpPost starts an HTTP POST operation to post resource to URL/path func HttpPost(url string, paths ...string) *http.ResourceWriter { - return DefaultEcho.HttpPost(url, paths...) + return DefaultEcho.HttpPostWithContext(context.Background(), url, paths...) } // Post is a convenient alias for HttpPost to post data at specified URL diff --git a/http.go b/http.go index 2a4622d..71c6e0b 100644 --- a/http.go +++ b/http.go @@ -1,29 +1,40 @@ package gexe import ( + "context" "strings" "github.com/vladimirvivien/gexe/http" ) -// HttpGet starts an HTTP GET operation to retrieve server resource from given URL/paths. -func (e *Echo) HttpGet(url string, paths ...string) *http.ResourceReader { +// HttpGetWithContext uses context ctx to start an HTTP GET operation to retrieve server resource from given URL/paths. +func (e *Echo) HttpGetWithContext(ctx context.Context, url string, paths ...string) *http.ResourceReader { var exapandedUrl strings.Builder exapandedUrl.WriteString(e.vars.Eval(url)) for _, path := range paths { exapandedUrl.WriteString(e.vars.Eval(path)) } - return http.GetWithVars(exapandedUrl.String(), e.vars) + return http.GetWithContextVars(ctx, exapandedUrl.String(), e.vars) } -// HttpPost starts an HTTP POST operation to post resource to a server at given URL/path. -func (e *Echo) HttpPost(url string, paths ...string) *http.ResourceWriter { +// HttpGetWithContext starts an HTTP GET operation to retrieve server resource from given URL/paths. +func (e *Echo) HttpGet(url string, paths ...string) *http.ResourceReader { + return e.HttpGetWithContext(context.Background(), url, paths...) +} + +// HttpPostWithContext uses context ctx to start an HTTP POST operation to post resource to a server at given URL/path. +func (e *Echo) HttpPostWithContext(ctx context.Context, url string, paths ...string) *http.ResourceWriter { var exapandedUrl strings.Builder exapandedUrl.WriteString(e.vars.Eval(url)) for _, path := range paths { exapandedUrl.WriteString(e.vars.Eval(path)) } - return http.PostWithVars(exapandedUrl.String(), e.vars) + return http.PostWithContextVars(ctx, exapandedUrl.String(), e.vars) +} + +// HttpPost starts an HTTP POST operation to post resource to a server at given URL/path. +func (e *Echo) HttpPost(url string, paths ...string) *http.ResourceWriter { + return e.HttpPostWithContext(context.Background(), url, paths...) } // Get is convenient alias for HttpGet to retrieve a resource at given URL/path diff --git a/http/http_reader.go b/http/http_reader.go index d67cb2f..1dc380f 100644 --- a/http/http_reader.go +++ b/http/http_reader.go @@ -1,7 +1,11 @@ package http import ( + "bytes" + "context" + "io" "net/http" + "strings" "time" "github.com/vladimirvivien/gexe/vars" @@ -9,22 +13,38 @@ import ( // ResourceReader provides types and methods to read content of resources from a server using HTTP type ResourceReader struct { - client *http.Client - err error - url string - vars *vars.Variables + client *http.Client + err error + url string + vars *vars.Variables + ctx context.Context + data io.Reader + headers http.Header } -// Get initiates a "GET" operation for the specified resource -func Get(url string) *ResourceReader { - return &ResourceReader{url: url, client: &http.Client{}, vars: &vars.Variables{}} +// GetWithContextVars uses context ctx and session variables to initiate +// a "GET" operation for the specified resource +func GetWithContextVars(ctx context.Context, url string, variables *vars.Variables) *ResourceReader { + if variables == nil { + variables = &vars.Variables{} + } + + return &ResourceReader{ + ctx: ctx, + url: variables.Eval(url), + client: &http.Client{}, + vars: &vars.Variables{}, + } } -// Get initiates a "GET" operation and sets session variables +// GetWithVars uses session vars to initiate a "GET" operation func GetWithVars(url string, variables *vars.Variables) *ResourceReader { - r := Get(variables.Eval(url)) - r.vars = variables - return r + return GetWithContextVars(context.Background(), url, variables) +} + +// Get initiates a "GET" operation for the specified resource +func Get(url string) *ResourceReader { + return GetWithContextVars(context.Background(), url, &vars.Variables{}) } // SetVars sets session variables for ResourceReader @@ -44,12 +64,60 @@ func (r *ResourceReader) WithTimeout(to time.Duration) *ResourceReader { return r } +// WithContext sets the context for the HTTP request +func (r *ResourceReader) WithContext(ctx context.Context) *ResourceReader { + r.ctx = ctx + return r +} + +// WithHeaders sets all HTTP headers for GET request +func (r *ResourceReader) WithHeaders(h http.Header) *ResourceReader { + r.headers = h + return r +} + +// AddHeader convenience method to add request header +func (r *ResourceReader) AddHeader(key, value string) *ResourceReader { + r.headers.Add(r.vars.Eval(key), r.vars.Eval(value)) + return r +} + +// SetHeader convenience method to set a specific header +func (r *ResourceReader) SetHeader(key, value string) *ResourceReader { + r.headers.Set(r.vars.Eval(key), r.vars.Eval(value)) + return r +} + +// RequestString sets GET request data as string +func (r *ResourceReader) String(val string) *ResourceReader { + r.data = strings.NewReader(r.vars.Eval(val)) + return r +} + +// RequestBytes sets GET request data as byte slice +func (r *ResourceReader) Bytes(data []byte) *ResourceReader { + r.data = bytes.NewReader(data) + return r +} + +// RequestBody sets GET request content as io.Reader +func (r *ResourceReader) Body(body io.Reader) *ResourceReader { + r.data = body + return r +} + // Do is a terminal method that actually retrieves the HTTP resource from the server. // It returns a gexe/http/*Response instance that can be used to access the result. func (r *ResourceReader) Do() *Response { - res, err := r.client.Get(r.url) + req, err := http.NewRequestWithContext(r.ctx, "GET", r.url, r.data) if err != nil { return &Response{err: err} } + + res, err := r.client.Do(req) + if err != nil { + return &Response{err: err} + } + return &Response{stat: res.Status, statCode: res.StatusCode, body: res.Body} } diff --git a/http/http_writer.go b/http/http_writer.go index 7c2af13..fa077f9 100644 --- a/http/http_writer.go +++ b/http/http_writer.go @@ -2,6 +2,7 @@ package http import ( "bytes" + "context" "io" "net/http" "net/url" @@ -19,18 +20,33 @@ type ResourceWriter struct { headers http.Header data io.Reader vars *vars.Variables + ctx context.Context } -// Post starts an HTTP "POST" operation using the provided URL. -func Post(resource string) *ResourceWriter { - return &ResourceWriter{url: resource, client: &http.Client{}, headers: make(http.Header), vars: &vars.Variables{}} +// PostWithContextVars uses the specified context ctx and session variable to +// start an HTTP "POST" operation +func PostWithContextVars(ctx context.Context, resource string, variables *vars.Variables) *ResourceWriter { + if variables == nil { + variables = &vars.Variables{} + } + + return &ResourceWriter{ + ctx: ctx, + url: variables.Eval(resource), + client: &http.Client{}, + headers: make(http.Header), + vars: variables, + } } -// PostWithVars starts an HTTP "POST" operation and sets the provided gexe session variables +// PostWithVars uses session variables to start an HTTP "POST" operation func PostWithVars(resource string, variables *vars.Variables) *ResourceWriter { - w := Post(variables.Eval(resource)) - w.vars = variables - return w + return PostWithContextVars(context.Background(), resource, variables) +} + +// Post starts an HTTP "POST" operation using the provided URL. +func Post(resource string) *ResourceWriter { + return PostWithContextVars(context.Background(), resource, &vars.Variables{}) } // SetVars sets gexe session variables to be used by the post operation @@ -45,6 +61,12 @@ func (w *ResourceWriter) WithTimeout(to time.Duration) *ResourceWriter { return w } +// WithContext sets the context to be used for the HTTP request +func (w *ResourceWriter) WithContext(ctx context.Context) *ResourceWriter { + w.ctx = ctx + return w +} + // Err returns the last error generated by the ResourceWriter func (w *ResourceWriter) Err() error { return w.err @@ -98,7 +120,7 @@ func (w *ResourceWriter) FormData(val map[string][]string) *ResourceWriter { // Do is a terminal method that actually posts the HTTP request to the server. // It returns a gexe/http/*Response instance that can be used to access post result. func (w *ResourceWriter) Do() *Response { - req, err := http.NewRequest("POST", w.url, w.data) + req, err := http.NewRequestWithContext(w.ctx, "POST", w.url, w.data) if err != nil { return &Response{err: err} }