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

Allow context aware requests #73

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Slings store HTTP Request properties to simplify sending requests and decoding r
* Encode structs into URL query parameters
* Encode a form or JSON into the Request Body
* Receive JSON success or failure responses
* Control a request's lifetime via context

## Install

Expand Down Expand Up @@ -271,6 +272,29 @@ func (s *IssueService) ListByRepo(owner, repo string, params *IssueListParams) (
}
```

### Controlling lifetime via context
All the above functionality of a sling can be made context aware.

Getting a context aware request:
```go
ctx, cancel := context.WithTimeout(context.Background(),10*time.Second)
req, err := sling.New().Get("https://example.com").RequestWithContext(ctx)
```
Receiving in a context aware manner
```go
success := &struct{}{}
failure := &struct{}{}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
resp, err := sling.New().Path("https://example.com").Get("/foo").ReceiveWithContext(ctx,success,failure)
```
After making the request you can first check whether request completed in time before proceeding with the response:
```go
if errors.Is(err, context.DeadlineExceeded) {
// Take action accordingly
}
```
For more details about effectively using context please see: https://go.dev/blog/context

## Example APIs using Sling

* Digits [dghubble/go-digits](https://github.com/dghubble/go-digits)
Expand Down
23 changes: 21 additions & 2 deletions sling.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sling

import (
"context"
"encoding/base64"
"io"
"net/http"
Expand Down Expand Up @@ -277,6 +278,15 @@ func (s *Sling) BodyForm(bodyForm interface{}) *Sling {
// Returns any errors parsing the rawURL, encoding query structs, encoding
// the body, or creating the http.Request.
func (s *Sling) Request() (*http.Request, error) {
return s.request(context.Background())
}

// RequestWithContext is similar to Request but allows you to pass a context.
func (s *Sling) RequestWithContext(ctx context.Context) (*http.Request, error) {
return s.request(ctx)
}

func (s *Sling) request(ctx context.Context) (*http.Request, error) {
reqURL, err := url.Parse(s.rawURL)
if err != nil {
return nil, err
Expand All @@ -294,7 +304,7 @@ func (s *Sling) Request() (*http.Request, error) {
return nil, err
}
}
req, err := http.NewRequest(s.method, reqURL.String(), body)
req, err := http.NewRequestWithContext(ctx, s.method, reqURL.String(), body)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -364,7 +374,16 @@ func (s *Sling) ReceiveSuccess(successV interface{}) (*http.Response, error) {
// the response is returned.
// Receive is shorthand for calling Request and Do.
func (s *Sling) Receive(successV, failureV interface{}) (*http.Response, error) {
req, err := s.Request()
return s.receive(context.Background(), successV, failureV)
}

// ReceiveWithContext is similar to Receive but allows you to pass a context.
func (s *Sling) ReceiveWithContext(ctx context.Context, successV, failureV interface{}) (*http.Response, error) {
return s.receive(ctx, successV, failureV)
}

func (s *Sling) receive(ctx context.Context, successV, failureV interface{}) (*http.Response, error) {
req, err := s.RequestWithContext(ctx)
if err != nil {
return nil, err
}
Expand Down
34 changes: 34 additions & 0 deletions sling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,20 @@ func TestRequest_headers(t *testing.T) {
}
}

func TestRequest_context(t *testing.T) {
ctx, fn := context.WithCancel(context.Background())
defer fn() // cancel context eventually to release resources

req, err := New().RequestWithContext(ctx)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}

if req.Context() != ctx {
t.Errorf("request.Context() is not same as context passed during request creation")
}
}

func TestAddQueryStructs(t *testing.T) {
cases := []struct {
rawurl string
Expand Down Expand Up @@ -924,6 +938,26 @@ func TestReceive_errorCreatingRequest(t *testing.T) {
}
}

func TestReceive_context(t *testing.T) {
client, mux, server := testServer()
defer server.Close()
mux.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
})

ctx, fn := context.WithCancel(context.Background())
defer fn()

endpoint := New().Client(client).Get("http://example.com/foo")
resp, err := endpoint.New().ReceiveWithContext(ctx, nil, nil)
if err != nil {
t.Errorf("expected nil, got %v", err)
}

if resp.Request.Context() != ctx {
t.Error("request.Context() is not same as context passed during receive operation")
}
}

func TestReuseTcpConnections(t *testing.T) {
var connCount int32

Expand Down