Skip to content

Commit

Permalink
Merge pull request #46 from ei-grad/set-context
Browse files Browse the repository at this point in the history
Add Request.SetContext for go1.7 and above
  • Loading branch information
jeevatkm authored Dec 29, 2016
2 parents f593e5e + 8024c33 commit cca8945
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 32 deletions.
9 changes: 9 additions & 0 deletions context17_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// +build !go1.8

package resty

import "strings"

func errIsContextCanceled(err error) bool {
return strings.Contains(err.Error(), "request canceled")
}
16 changes: 16 additions & 0 deletions context18_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// +build go1.8

package resty

import (
"context"
"net/url"
)

func errIsContextCanceled(err error) bool {
ue, ok := err.(*url.Error)
if !ok {
return false
}
return ue.Err == context.Canceled
}
191 changes: 191 additions & 0 deletions context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// +build go1.7

package resty

import (
"context"
"net/http"
"testing"
"time"
)

func TestSetContext(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()

resp, err := R().
SetContext(context.Background()).
Get(ts.URL + "/")

assertError(t, err)
assertEqual(t, http.StatusOK, resp.StatusCode())
assertEqual(t, "200 OK", resp.Status())
assertEqual(t, true, resp.Body() != nil)
assertEqual(t, "TestGet: text response", resp.String())

logResponse(t, resp)
}

func TestSetContextWithError(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()

resp, err := dcr().
SetContext(context.Background()).
Get(ts.URL + "/mypage")

assertError(t, err)
assertEqual(t, http.StatusBadRequest, resp.StatusCode())
assertEqual(t, "", resp.String())

logResponse(t, resp)
}

func TestSetContextCancel(t *testing.T) {
ch := make(chan struct{})
ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
defer func() {
ch <- struct{}{} // tell test request is finished
}()
t.Logf("Server: %v %v", r.Method, r.URL.Path)
ch <- struct{}{}
<-ch // wait for client to finish request
n, err := w.Write([]byte("TestSetContextCancel: response"))
// FIXME? test server doesn't handle request cancellation
t.Logf("Server: wrote %d bytes", n)
t.Logf("Server: err is %v ", err)
})
defer ts.Close()

ctx, cancel := context.WithCancel(context.Background())

go func() {
<-ch // wait for server to start request handling
cancel()
}()

_, err := R().
SetContext(ctx).
Get(ts.URL + "/")

ch <- struct{}{} // tell server to continue request handling

<-ch // wait for server to finish request handling

t.Logf("Error: %v", err)
if !errIsContextCanceled(err) {
t.Errorf("Got unexpected error: %v", err)
}
}

func TestSetContextCancelRetry(t *testing.T) {
reqCount := 0
ch := make(chan struct{})
ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
reqCount++
defer func() {
ch <- struct{}{} // tell test request is finished
}()
t.Logf("Server: %v %v", r.Method, r.URL.Path)
ch <- struct{}{}
<-ch // wait for client to finish request
n, err := w.Write([]byte("TestSetContextCancel: response"))
// FIXME? test server doesn't handle request cancellation
t.Logf("Server: wrote %d bytes", n)
t.Logf("Server: err is %v ", err)
})
defer ts.Close()

ctx, cancel := context.WithCancel(context.Background())

go func() {
<-ch // wait for server to start request handling
cancel()
}()

c := dc()
c.SetHTTPMode().
SetTimeout(time.Duration(time.Second * 3)).
SetRetryCount(3)

_, err := c.R().
SetContext(ctx).
Get(ts.URL + "/")

ch <- struct{}{} // tell server to continue request handling

<-ch // wait for server to finish request handling

t.Logf("Error: %v", err)
if !errIsContextCanceled(err) {
t.Errorf("Got unexpected error: %v", err)
}

if reqCount != 1 {
t.Errorf("Request was retried %d times instead of 1", reqCount)
}
}

func TestSetContextCancelWithError(t *testing.T) {
ch := make(chan struct{})
ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
defer func() {
ch <- struct{}{} // tell test request is finished
}()
t.Logf("Server: %v %v", r.Method, r.URL.Path)
t.Log("Server: sending StatusBadRequest response")
w.WriteHeader(http.StatusBadRequest)
ch <- struct{}{}
<-ch // wait for client to finish request
n, err := w.Write([]byte("TestSetContextCancelWithError: response"))
// FIXME? test server doesn't handle request cancellation
t.Logf("Server: wrote %d bytes", n)
t.Logf("Server: err is %v ", err)
})
defer ts.Close()

ctx, cancel := context.WithCancel(context.Background())

go func() {
<-ch // wait for server to start request handling
cancel()
}()

_, err := R().
SetContext(ctx).
Get(ts.URL + "/")

ch <- struct{}{} // tell server to continue request handling

<-ch // wait for server to finish request handling

t.Logf("Error: %v", err)
if !errIsContextCanceled(err) {
t.Errorf("Got unexpected error: %v", err)
}
}

func TestClientRetryWithSetContext(t *testing.T) {
attempt := 0
ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
t.Logf("Method: %v", r.Method)
t.Logf("Path: %v", r.URL.Path)
attempt++
if attempt != 3 {
time.Sleep(time.Second * 2)
}
w.Write([]byte("TestClientRetry page"))
})
defer ts.Close()

c := dc()
c.SetHTTPMode().
SetTimeout(time.Duration(time.Second * 1)).
SetRetryCount(3)

_, err := c.R().
SetContext(context.Background()).
Get(ts.URL + "/")

assertError(t, err)
}
3 changes: 3 additions & 0 deletions middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ func createHTTPRequest(c *Client, r *Request) (err error) {
r.RawRequest.URL.Host = r.URL
}

// Use context if it was specified
r.addContextIfAvailable()

return
}

Expand Down
37 changes: 5 additions & 32 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,11 @@ import (
"encoding/xml"
"fmt"
"io"
"net/http"
"net/url"
"reflect"
"strings"
"time"
)

// Request type is used to compose and send individual request from client
// go-resty is provide option override client level settings such as
// Auth Token, Basic Auth credentials, Header, Query Param, Form Data, Error object
// and also you can add more options for that particular request
//
type Request struct {
URL string
Method string
QueryParam url.Values
FormData url.Values
Header http.Header
UserInfo *User
Token string
Body interface{}
Result interface{}
Error interface{}
Time time.Time
RawRequest *http.Request

client *Client
bodyBuf *bytes.Buffer
isMultiPart bool
isFormData bool
setContentLength bool
isSaveResponse bool
outputFile string
proxyURL *url.URL
multipartFiles []*File
}

// SetHeader method is to set a single header field and its value in the current request.
// Example: To set `Content-Type` and `Accept` as `application/json`.
// resty.R().
Expand Down Expand Up @@ -440,6 +408,11 @@ func (r *Request) Execute(method, url string) (*Response, error) {
resp, err = r.client.execute(r)
if err != nil {
r.client.Log.Printf("ERROR [%v] Attempt [%v]", err, attempt)
if r.isContextCancelledIfAvailable() {
// stop Backoff from retrying request if request has been
// canceled by context
return resp, nil
}
}

return resp, err
Expand Down
49 changes: 49 additions & 0 deletions request16.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// +build !go1.7

package resty

import (
"bytes"
"net/http"
"net/url"
"time"
)

// Request type is used to compose and send individual request from client
// go-resty is provide option override client level settings such as
// Auth Token, Basic Auth credentials, Header, Query Param, Form Data, Error object
// and also you can add more options for that particular request
//
type Request struct {
URL string
Method string
QueryParam url.Values
FormData url.Values
Header http.Header
UserInfo *User
Token string
Body interface{}
Result interface{}
Error interface{}
Time time.Time
RawRequest *http.Request

client *Client
bodyBuf *bytes.Buffer
isMultiPart bool
isFormData bool
setContentLength bool
isSaveResponse bool
outputFile string
proxyURL *url.URL
multipartFiles []*File
}

func (r *Request) addContextIfAvailable() {
// nothing to do for golang<1.7
}

func (r *Request) isContextCancelledIfAvailable() bool {
// just always return false golang<1.7
return false
}
Loading

0 comments on commit cca8945

Please sign in to comment.