1 // Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
2 // resty source code and usage is governed by a MIT style
3 // license that can be found in the LICENSE file.
4 5 package resty
6 7 import (
8 "encoding/json"
9 "fmt"
10 "io"
11 "net/http"
12 "strings"
13 "time"
14 )
15 16 //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
17 // Response struct and methods
18 //_______________________________________________________________________
19 20 // Response struct holds response values of executed requests.
21 type Response struct {
22 Request *Request
23 RawResponse *http.Response
24 25 body []byte
26 size int64
27 receivedAt time.Time
28 }
29 30 // Body method returns the HTTP response as `[]byte` slice for the executed request.
31 //
32 // NOTE: [Response.Body] might be nil if [Request.SetOutput] is used.
33 // Also see [Request.SetDoNotParseResponse], [Client.SetDoNotParseResponse]
34 func (r *Response) Body() []byte {
35 if r.RawResponse == nil {
36 return []byte{}
37 }
38 return r.body
39 }
40 41 // SetBody method sets [Response] body in byte slice. Typically,
42 // It is helpful for test cases.
43 //
44 // resp.SetBody([]byte("This is test body content"))
45 // resp.SetBody(nil)
46 func (r *Response) SetBody(b []byte) *Response {
47 r.body = b
48 return r
49 }
50 51 // Status method returns the HTTP status string for the executed request.
52 //
53 // Example: 200 OK
54 func (r *Response) Status() string {
55 if r.RawResponse == nil {
56 return ""
57 }
58 return r.RawResponse.Status
59 }
60 61 // StatusCode method returns the HTTP status code for the executed request.
62 //
63 // Example: 200
64 func (r *Response) StatusCode() int {
65 if r.RawResponse == nil {
66 return 0
67 }
68 return r.RawResponse.StatusCode
69 }
70 71 // Proto method returns the HTTP response protocol used for the request.
72 func (r *Response) Proto() string {
73 if r.RawResponse == nil {
74 return ""
75 }
76 return r.RawResponse.Proto
77 }
78 79 // Result method returns the response value as an object if it has one
80 //
81 // See [Request.SetResult]
82 func (r *Response) Result() interface{} {
83 return r.Request.Result
84 }
85 86 // Error method returns the error object if it has one
87 //
88 // See [Request.SetError], [Client.SetError]
89 func (r *Response) Error() interface{} {
90 return r.Request.Error
91 }
92 93 // Header method returns the response headers
94 func (r *Response) Header() http.Header {
95 if r.RawResponse == nil {
96 return http.Header{}
97 }
98 return r.RawResponse.Header
99 }
100 101 // Cookies method to returns all the response cookies
102 func (r *Response) Cookies() []*http.Cookie {
103 if r.RawResponse == nil {
104 return make([]*http.Cookie, 0)
105 }
106 return r.RawResponse.Cookies()
107 }
108 109 // String method returns the body of the HTTP response as a `string`.
110 // It returns an empty string if it is nil or the body is zero length.
111 func (r *Response) String() string {
112 if len(r.body) == 0 {
113 return ""
114 }
115 return strings.TrimSpace(string(r.body))
116 }
117 118 // Time method returns the duration of HTTP response time from the request we sent
119 // and received a request.
120 //
121 // See [Response.ReceivedAt] to know when the client received a response and see
122 // `Response.Request.Time` to know when the client sent a request.
123 func (r *Response) Time() time.Duration {
124 if r.Request.clientTrace != nil {
125 return r.Request.TraceInfo().TotalTime
126 }
127 return r.receivedAt.Sub(r.Request.Time)
128 }
129 130 // ReceivedAt method returns the time we received a response from the server for the request.
131 func (r *Response) ReceivedAt() time.Time {
132 return r.receivedAt
133 }
134 135 // Size method returns the HTTP response size in bytes. Yeah, you can rely on HTTP `Content-Length`
136 // header, however it won't be available for chucked transfer/compressed response.
137 // Since Resty captures response size details when processing the response body
138 // when possible. So that users get the actual size of response bytes.
139 func (r *Response) Size() int64 {
140 return r.size
141 }
142 143 // RawBody method exposes the HTTP raw response body. Use this method in conjunction with
144 // [Client.SetDoNotParseResponse] or [Request.SetDoNotParseResponse]
145 // option; otherwise, you get an error as `read err: http: read on closed response body.`
146 //
147 // Do not forget to close the body, otherwise you might get into connection leaks, no connection reuse.
148 // You have taken over the control of response parsing from Resty.
149 func (r *Response) RawBody() io.ReadCloser {
150 if r.RawResponse == nil {
151 return nil
152 }
153 return r.RawResponse.Body
154 }
155 156 // IsSuccess method returns true if HTTP status `code >= 200 and <= 299` otherwise false.
157 func (r *Response) IsSuccess() bool {
158 return r.StatusCode() > 199 && r.StatusCode() < 300
159 }
160 161 // IsError method returns true if HTTP status `code >= 400` otherwise false.
162 func (r *Response) IsError() bool {
163 return r.StatusCode() > 399
164 }
165 166 func (r *Response) setReceivedAt() {
167 r.receivedAt = time.Now()
168 if r.Request.clientTrace != nil {
169 r.Request.clientTrace.endTime = r.receivedAt
170 }
171 }
172 173 func (r *Response) fmtBodyString(sl int64) string {
174 if r.Request.client.notParseResponse || r.Request.notParseResponse {
175 return "***** DO NOT PARSE RESPONSE - Enabled *****"
176 }
177 if len(r.body) > 0 {
178 if int64(len(r.body)) > sl {
179 return fmt.Sprintf("***** RESPONSE TOO LARGE (size - %d) *****", len(r.body))
180 }
181 ct := r.Header().Get(hdrContentTypeKey)
182 if IsJSONType(ct) {
183 out := acquireBuffer()
184 defer releaseBuffer(out)
185 err := json.Indent(out, r.body, "", " ")
186 if err != nil {
187 return fmt.Sprintf("*** Error: Unable to format response body - \"%s\" ***\n\nLog Body as-is:\n%s", err, r.String())
188 }
189 return out.String()
190 }
191 return r.String()
192 }
193 194 return "***** NO CONTENT *****"
195 }
196