Skip to content

Commit 4bf8588

Browse files
committed
Supports saving large file size too, code & test improvements.
1 parent 7d446cd commit 4bf8588

File tree

5 files changed

+82
-55
lines changed

5 files changed

+82
-55
lines changed

client.go

+19-5
Original file line numberDiff line numberDiff line change
@@ -571,10 +571,14 @@ func (c *Client) execute(req *Request) (*Response, error) {
571571
RawResponse: resp,
572572
}
573573

574-
defer resp.Body.Close()
575-
response.Body, err = ioutil.ReadAll(resp.Body)
576-
if err != nil {
577-
return nil, err
574+
if !req.isSaveResponse {
575+
defer resp.Body.Close()
576+
response.Body, err = ioutil.ReadAll(resp.Body)
577+
if err != nil {
578+
return nil, err
579+
}
580+
581+
response.size = int64(len(response.Body))
578582
}
579583

580584
// Apply Response middleware
@@ -890,6 +894,7 @@ func (r *Request) SetAuthToken(token string) *Request {
890894
// SetOutput("/Users/jeeva/Downloads/ReplyWithHeader-v5.1-beta.zip").
891895
// Get("http://bit.ly/1LouEKr")
892896
//
897+
// Note: In this scenario `Response.Body` might be nil.
893898
func (r *Request) SetOutput(file string) *Request {
894899
r.outputFile = file
895900
r.isSaveResponse = true
@@ -960,6 +965,8 @@ type Response struct {
960965
ReceivedAt time.Time
961966
Request *Request
962967
RawResponse *http.Response
968+
969+
size int64
963970
}
964971

965972
// Status method returns the HTTP status string for the executed request.
@@ -1010,6 +1017,13 @@ func (r *Response) Time() time.Duration {
10101017
return r.ReceivedAt.Sub(r.Request.Time)
10111018
}
10121019

1020+
// Size method returns the HTTP response size in bytes. Ya, you can relay on HTTP `Content-Length` header,
1021+
// however it won't be good for chucked transfer/compressed response. Since Resty calculates response size
1022+
// at the client end. You will get actual size of the http response.
1023+
func (r *Response) Size() int64 {
1024+
return r.size
1025+
}
1026+
10131027
//
10141028
// Helper methods
10151029
//
@@ -1167,7 +1181,7 @@ func createDirectory(dir string) (err error) {
11671181
if _, err = os.Stat(dir); err != nil {
11681182
if os.IsNotExist(err) {
11691183
if err = os.MkdirAll(dir, 0755); err != nil {
1170-
return err
1184+
return
11711185
}
11721186
}
11731187
}

middleware.go

+23-10
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ import (
1010
"encoding/xml"
1111
"errors"
1212
"fmt"
13-
"io/ioutil"
13+
"io"
1414
"mime/multipart"
1515
"net/http"
1616
"net/url"
17+
"os"
1718
"path/filepath"
1819
"reflect"
1920
"strings"
@@ -250,7 +251,11 @@ func responseLogger(c *Client, res *Response) error {
250251
for h, v := range res.Header() {
251252
c.Log.Printf("%30s: %v", h, strings.Join(v, ", "))
252253
}
253-
c.Log.Printf("BODY :\n%v", getResponseBodyString(res))
254+
if res.Request.isSaveResponse {
255+
c.Log.Printf("BODY :\n***** RESPONSE WRITTEN INTO FILE *****")
256+
} else {
257+
c.Log.Printf("BODY :\n%v", getResponseBodyString(res))
258+
}
254259
c.Log.Println("----------------------------------------------------------")
255260
c.enableLogPrefix()
256261
}
@@ -285,27 +290,35 @@ func parseResponseBody(c *Client, res *Response) (err error) {
285290
return
286291
}
287292

288-
func saveResponseIntoFile(c *Client, res *Response) (err error) {
293+
func saveResponseIntoFile(c *Client, res *Response) error {
289294
if res.Request.isSaveResponse {
290295
file := ""
291296

292297
if len(c.outputDirectory) > 0 && !filepath.IsAbs(res.Request.outputFile) {
293298
file += c.outputDirectory + string(filepath.Separator)
294299
}
300+
295301
file = filepath.Clean(file + res.Request.outputFile)
302+
err := createDirectory(filepath.Dir(file))
303+
if err != nil {
304+
return err
305+
}
296306

297-
err = createDirectory(filepath.Dir(file))
307+
outFile, err := os.Create(file)
298308
if err != nil {
299-
c.Log.Printf("ERROR [%v]", err)
300-
return
309+
return err
301310
}
311+
defer outFile.Close()
302312

303-
err = ioutil.WriteFile(file, res.Body, 0644)
313+
// io.Copy reads maximum 32kb size, it is perfect for large file download too
314+
defer res.RawResponse.Body.Close()
315+
written, err := io.Copy(outFile, res.RawResponse.Body)
304316
if err != nil {
305-
c.Log.Printf("ERROR [%v]", err)
306-
return
317+
return err
307318
}
319+
320+
res.size = written
308321
}
309322

310-
return
323+
return nil
311324
}

redirect.go

+11-3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func FlexibleRedirectPolicy(noOfRedirect int) RedirectPolicy {
5050
}
5151

5252
// DomainCheckRedirectPolicy is convenient method to define domain name redirect rule in resty client.
53+
// Redirect is allowed for only mentioned host in the policy.
5354
// resty.SetRedirectPolicy(DomainCheckRedirectPolicy("host1.com", "host2.org", "host3.net"))
5455
func DomainCheckRedirectPolicy(hostnames ...string) RedirectPolicy {
5556
hosts := make(map[string]bool)
@@ -58,9 +59,16 @@ func DomainCheckRedirectPolicy(hostnames ...string) RedirectPolicy {
5859
}
5960

6061
fn := RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
61-
host, _, _ := net.SplitHostPort(req.URL.Host)
62-
if _, ok := hosts[strings.ToLower(host)]; ok {
63-
return fmt.Errorf("Redirect is not allowed for host[%s]", strings.ToLower(host))
62+
hostname := ""
63+
if strings.Index(req.URL.Host, ":") > 0 {
64+
host, _, _ := net.SplitHostPort(req.URL.Host)
65+
hostname = strings.ToLower(host)
66+
} else {
67+
hostname = strings.ToLower(req.URL.Host)
68+
}
69+
70+
if ok := hosts[hostname]; !ok {
71+
return errors.New("Redirect is not allowed as per DomainCheckRedirectPolicy")
6472
}
6573

6674
return nil

resty.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ Package resty provides simple HTTP and REST client for Go inspired by Ruby rest-
99
package resty
1010

1111
// go-resty version no
12-
var Version = "0.4"
12+
var Version = "0.4.1"

resty_test.go

+28-36
Original file line numberDiff line numberDiff line change
@@ -733,10 +733,12 @@ func TestHostCheckRedirectPolicy(t *testing.T) {
733733
defer ts.Close()
734734

735735
c := dc().
736-
SetRedirectPolicy(DomainCheckRedirectPolicy("NotAllowed.com")).
737-
SetTimeout(time.Duration(time.Second * 2))
736+
SetRedirectPolicy(DomainCheckRedirectPolicy("127.0.0.1"))
738737

739-
c.R().Get(ts.URL + "/redirect-host-check-1")
738+
_, err := c.R().Get(ts.URL + "/redirect-host-check-1")
739+
740+
assertEqual(t, true, err != nil)
741+
assertEqual(t, true, strings.Contains(err.Error(), "Redirect is not allowed as per DomainCheckRedirectPolicy"))
740742
}
741743

742744
func TestClientRedirectPolicy(t *testing.T) {
@@ -917,54 +919,39 @@ func TestSetRootCertificateNotExists(t *testing.T) {
917919
assertEqual(t, true, DefaultClient.transport.TLSClientConfig == nil)
918920
}
919921

920-
func TestOutputFileRelativePath(t *testing.T) {
921-
c := dc().SetRedirectPolicy(FlexibleRedirectPolicy(10))
922-
923-
_, err := c.R().
924-
SetOutput("go-resty/ReplyWithHeader-v5.1-beta.zip").
925-
Get("http://bit.ly/1LouEKr")
926-
927-
assertError(t, err)
928-
}
922+
func TestOutputFileWithBaseDirAndRelativePath(t *testing.T) {
923+
ts := createGetServer(t)
924+
defer ts.Close()
929925

930-
func TestOutputFileWithBaseDir(t *testing.T) {
931926
DefaultClient = dc()
932-
933927
SetRedirectPolicy(FlexibleRedirectPolicy(10))
934-
SetOutputDirectory(getTestDataPath() + "/sample1")
935-
936-
_, err := R().
937-
SetOutput("go-resty/ReplyWithHeader-v5.1-beta.zip").
938-
Get("http://bit.ly/1LouEKr")
939-
940-
assertError(t, err)
941-
}
942-
943-
func TestOutputFileAbsPath(t *testing.T) {
944-
c := dc().SetRedirectPolicy(FlexibleRedirectPolicy(10))
928+
SetOutputDirectory(getTestDataPath() + "/dir-sample")
929+
SetDebug(true)
945930

946-
_, err := c.R().
947-
SetOutput(getTestDataPath() + "/go-resty-test-2/ReplyWithHeader-v5.1-beta.zip").
948-
Get("http://bit.ly/1LouEKr")
931+
resp, err := R().
932+
SetOutput("go-resty/test-img-success.png").
933+
Get(ts.URL + "/my-image.png")
949934

950935
assertError(t, err)
936+
assertEqual(t, true, resp.Size() != 0)
951937
}
952938

953939
func TestOutputFileWithBaseDirError(t *testing.T) {
954940
c := dc().SetRedirectPolicy(FlexibleRedirectPolicy(10)).
955-
SetOutputDirectory("/go-resty")
941+
SetOutputDirectory(getTestDataPath() + `/go-resty\0`)
956942

957943
_ = c
958944
}
959945

960-
func TestOutputFileAbsPathError(t *testing.T) {
961-
c := dc().SetRedirectPolicy(FlexibleRedirectPolicy(10))
946+
func TestOutputFileAbsPath(t *testing.T) {
947+
ts := createGetServer(t)
948+
defer ts.Close()
962949

963-
_, err := c.R().
964-
SetOutput("/go-resty-test-2/ReplyWithHeader-v5.1-beta.zip").
965-
Get("http://bit.ly/1LouEKr")
950+
_, err := dcr().
951+
SetOutput(getTestDataPath() + "/go-resty/test-img-success-2.png").
952+
Get(ts.URL + "/my-image.png")
966953

967-
assertEqual(t, true, err != nil)
954+
assertError(t, err)
968955
}
969956

970957
func TestClientOptions(t *testing.T) {
@@ -1071,6 +1058,11 @@ func createGetServer(t *testing.T) *httptest.Server {
10711058
} else if r.URL.Path == "/set-timeout-test" {
10721059
time.Sleep(time.Second * 5)
10731060
w.Write([]byte("TestClientTimeout page"))
1061+
} else if r.URL.Path == "/my-image.png" {
1062+
fileBytes, _ := ioutil.ReadFile(getTestDataPath() + "/test-img.png")
1063+
w.Header().Set("Content-Type", "image/png")
1064+
w.Header().Set("Content-Length", strconv.Itoa(len(fileBytes)))
1065+
w.Write(fileBytes)
10741066
}
10751067
}
10761068
})
@@ -1301,7 +1293,7 @@ func createRedirectServer(t *testing.T) *httptest.Server {
13011293

13021294
if cnt != 7 { // Testing hard stop via logical
13031295
if cnt >= 5 {
1304-
http.Redirect(w, r, "http://notallowed.com/go-redirect", http.StatusTemporaryRedirect)
1296+
http.Redirect(w, r, "http://httpbin.org/get", http.StatusTemporaryRedirect)
13051297
} else {
13061298
http.Redirect(w, r, fmt.Sprintf("/redirect-host-check-%d", (cnt+1)), http.StatusTemporaryRedirect)
13071299
}

0 commit comments

Comments
 (0)