1// Copyright (c) 2015-2019 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
5package resty
6
7import (
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 request.
21type 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 HTTP response as []byte array for the executed request.
31//
32// Note: `Response.Body` might be nil, if `Request.SetOutput` is used.
33func (r *Response) Body() []byte {
34	if r.RawResponse == nil {
35		return []byte{}
36	}
37	return r.body
38}
39
40// Status method returns the HTTP status string for the executed request.
41//	Example: 200 OK
42func (r *Response) Status() string {
43	if r.RawResponse == nil {
44		return ""
45	}
46	return r.RawResponse.Status
47}
48
49// StatusCode method returns the HTTP status code for the executed request.
50//	Example: 200
51func (r *Response) StatusCode() int {
52	if r.RawResponse == nil {
53		return 0
54	}
55	return r.RawResponse.StatusCode
56}
57
58// Result method returns the response value as an object if it has one
59func (r *Response) Result() interface{} {
60	return r.Request.Result
61}
62
63// Error method returns the error object if it has one
64func (r *Response) Error() interface{} {
65	return r.Request.Error
66}
67
68// Header method returns the response headers
69func (r *Response) Header() http.Header {
70	if r.RawResponse == nil {
71		return http.Header{}
72	}
73	return r.RawResponse.Header
74}
75
76// Cookies method to access all the response cookies
77func (r *Response) Cookies() []*http.Cookie {
78	if r.RawResponse == nil {
79		return make([]*http.Cookie, 0)
80	}
81	return r.RawResponse.Cookies()
82}
83
84// String method returns the body of the server response as String.
85func (r *Response) String() string {
86	if r.body == nil {
87		return ""
88	}
89	return strings.TrimSpace(string(r.body))
90}
91
92// Time method returns the time of HTTP response time that from request we sent and received a request.
93//
94// See `Response.ReceivedAt` to know when client recevied response and see `Response.Request.Time` to know
95// when client sent a request.
96func (r *Response) Time() time.Duration {
97	if r.Request.clientTrace != nil {
98		return r.receivedAt.Sub(r.Request.clientTrace.getConn)
99	}
100	return r.receivedAt.Sub(r.Request.Time)
101}
102
103// ReceivedAt method returns when response got recevied from server for the request.
104func (r *Response) ReceivedAt() time.Time {
105	return r.receivedAt
106}
107
108// Size method returns the HTTP response size in bytes. Ya, you can relay on HTTP `Content-Length` header,
109// however it won't be good for chucked transfer/compressed response. Since Resty calculates response size
110// at the client end. You will get actual size of the http response.
111func (r *Response) Size() int64 {
112	return r.size
113}
114
115// RawBody method exposes the HTTP raw response body. Use this method in-conjunction with `SetDoNotParseResponse`
116// option otherwise you get an error as `read err: http: read on closed response body`.
117//
118// Do not forget to close the body, otherwise you might get into connection leaks, no connection reuse.
119// Basically you have taken over the control of response parsing from `Resty`.
120func (r *Response) RawBody() io.ReadCloser {
121	if r.RawResponse == nil {
122		return nil
123	}
124	return r.RawResponse.Body
125}
126
127// IsSuccess method returns true if HTTP status `code >= 200 and <= 299` otherwise false.
128func (r *Response) IsSuccess() bool {
129	return r.StatusCode() > 199 && r.StatusCode() < 300
130}
131
132// IsError method returns true if HTTP status `code >= 400` otherwise false.
133func (r *Response) IsError() bool {
134	return r.StatusCode() > 399
135}
136
137//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
138// Response Unexported methods
139//_______________________________________________________________________
140
141func (r *Response) fmtBodyString(sl int64) string {
142	if r.body != nil {
143		if int64(len(r.body)) > sl {
144			return fmt.Sprintf("***** RESPONSE TOO LARGE (size - %d) *****", len(r.body))
145		}
146		ct := r.Header().Get(hdrContentTypeKey)
147		if IsJSONType(ct) {
148			out := acquireBuffer()
149			defer releaseBuffer(out)
150			err := json.Indent(out, r.body, "", "   ")
151			if err != nil {
152				return fmt.Sprintf("*** Error: Unable to format response body - \"%s\" ***\n\nLog Body as-is:\n%s", err, r.String())
153			}
154			return out.String()
155		}
156		return r.String()
157	}
158
159	return "***** NO CONTENT *****"
160}
161