response.go raw

   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