fiber/client/response.go
Jinquan Wang b38be4bcb3
v3 (feature): client refactor (#1986)
*  v3: Move the client module to the client folder and fix the error

*  v3: add xml encoder and decoder

* 🚧 v3: design plugin and hook mechanism, complete simple get request

* 🚧 v3: reset add some field

* 🚧 v3: add doc and fix some error

* 🚧 v3: add header merge

* 🚧 v3: add query param

* 🚧 v3: change to fasthttp's header and args

*  v3: add body and ua setting

* 🚧 v3: add cookie support

* 🚧 v3: add path param support

*  v3: fix error test case

* 🚧 v3: add formdata and file support

* 🚧 v3: referer support

* 🚧 v3: reponse unmarshal

*  v3: finish API design

* 🔥 v3: remove plugin mechanism

* 🚧 v3: add timeout

* 🚧 v3: change path params pattern and add unit test for core

* ✏️ v3: error spell

*  v3: improve test coverage

*  perf: change test func name to fit project format

* 🚧 v3: handle error

* 🚧 v3: add unit test and fix error

* ️ chore: change func to improve performance

*  v3: add some unit test

*  v3: fix error test

* 🐛 fix: add cookie to response

*  v3: add unit test

*  v3: export raw field

* 🐛 fix: fix data race

* 🔒️ chore: change package

* 🐛 fix: data race

* 🐛 fix: test fail

*  feat: move core to req

* 🐛 fix: connection reuse

* 🐛 fix: data race

* 🐛 fix: data race

* 🔀 fix: change to testify

*  fix: fail test in windows

*  feat: response body save to file

*  feat: support tls config

* 🐛 fix: add err check

* 🎨 perf: fix some static check

*  feat: add proxy support

*  feat: add retry feature

* 🐛 fix: static check error

* 🎨 refactor: move som code

* docs: change readme

*  feat: extend axios API

* perf: change field to export field

*  chore: disable startup message

* 🐛 fix: fix test error

* chore: fix error test

* chore: fix test case

* feat: add some test to client

* chore: add test case

* chore: add test case

*  feat: add peek for client

*  chore: add test case

* ️ feat: lazy generate rand string

* 🚧 perf: add config test case

* 🐛 fix: fix merge error

* 🐛 fix utils error

*  add redirection

* 🔥 chore: delete deps

* perf: fix spell error

* 🎨 perf: spell error

*  feat: add logger

*  feat: add cookie jar

*  feat: logger with level

* 🎨 perf: change the field name

* perf: add jar test

* fix proxy test

* improve test coverage

* fix proxy tests

* add cookiejar support from pending fasthttp PR

* fix some lint errors.

* add benchmark for SetValWithStruct

* optimize

* update

* fix proxy middleware

* use panicf instead of errorf and fix panic on default logger

* update

* update

* cleanup comments

* cleanup comments

* fix golang-lint errors

* Update helper_test.go

* add more test cases

* add hostclient pool

* make it more thread safe
-> there is still something which is shared between the requests

* fixed some golangci-lint errors

* fix Test_Request_FormData test

* create new test suite

* just create client for once

* use random port instead of 3000

* remove client pooling and fix test suite

* fix data races on logger tests

* fix proxy tests

* fix global tests

* remove unused code

* fix logger test

* fix proxy tests

* fix linter

* use lock instead of rlock

* fix cookiejar data-race

* fix(client): race conditions

* fix(client): race conditions

* apply some reviews

* change client property name

* apply review

* add parallel benchmark for simple request

* apply review

* apply review

* fix log tests

* fix linter

* fix(client): return error in SetProxyURL instead of panic

---------

Co-authored-by: Muhammed Efe Çetin <efectn@protonmail.com>
Co-authored-by: René Werner <rene.werner@verivox.com>
Co-authored-by: Joey <fenny@gofiber.io>
Co-authored-by: René <rene@gofiber.io>
2024-03-04 08:49:14 +01:00

185 lines
4.4 KiB
Go

package client
import (
"bytes"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"sync"
"github.com/gofiber/utils/v2"
"github.com/valyala/fasthttp"
)
// Response is the result of a request. This object is used to access the response data.
type Response struct {
client *Client
request *Request
cookie []*fasthttp.Cookie
RawResponse *fasthttp.Response
}
// setClient method sets client object in response instance.
// Use core object in the client.
func (r *Response) setClient(c *Client) {
r.client = c
}
// setRequest method sets Request object in response instance.
// The request will be released when the Response.Close is called.
func (r *Response) setRequest(req *Request) {
r.request = req
}
// Status method returns the HTTP status string for the executed request.
func (r *Response) Status() string {
return string(r.RawResponse.Header.StatusMessage())
}
// StatusCode method returns the HTTP status code for the executed request.
func (r *Response) StatusCode() int {
return r.RawResponse.StatusCode()
}
// Protocol method returns the HTTP response protocol used for the request.
func (r *Response) Protocol() string {
return string(r.RawResponse.Header.Protocol())
}
// Header method returns the response headers.
func (r *Response) Header(key string) string {
return utils.UnsafeString(r.RawResponse.Header.Peek(key))
}
// Cookies method to access all the response cookies.
func (r *Response) Cookies() []*fasthttp.Cookie {
return r.cookie
}
// Body method returns HTTP response as []byte array for the executed request.
func (r *Response) Body() []byte {
return r.RawResponse.Body()
}
// String method returns the body of the server response as String.
func (r *Response) String() string {
return strings.TrimSpace(string(r.Body()))
}
// JSON method will unmarshal body to json.
func (r *Response) JSON(v any) error {
return r.client.jsonUnmarshal(r.Body(), v)
}
// XML method will unmarshal body to xml.
func (r *Response) XML(v any) error {
return r.client.xmlUnmarshal(r.Body(), v)
}
// Save method will save the body to a file or io.Writer.
func (r *Response) Save(v any) error {
switch p := v.(type) {
case string:
file := filepath.Clean(p)
dir := filepath.Dir(file)
// create directory
if _, err := os.Stat(dir); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("failed to check directory: %w", err)
}
if err = os.MkdirAll(dir, 0o750); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
}
// create file
outFile, err := os.Create(file)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}
defer func() { _ = outFile.Close() }() //nolint:errcheck // not needed
_, err = io.Copy(outFile, bytes.NewReader(r.Body()))
if err != nil {
return fmt.Errorf("failed to write response body to file: %w", err)
}
return nil
case io.Writer:
_, err := io.Copy(p, bytes.NewReader(r.Body()))
if err != nil {
return fmt.Errorf("failed to write response body to io.Writer: %w", err)
}
defer func() {
if pc, ok := p.(io.WriteCloser); ok {
_ = pc.Close() //nolint:errcheck // not needed
}
}()
return nil
default:
return ErrNotSupportSaveMethod
}
}
// Reset clear Response object.
func (r *Response) Reset() {
r.client = nil
r.request = nil
for len(r.cookie) != 0 {
t := r.cookie[0]
r.cookie = r.cookie[1:]
fasthttp.ReleaseCookie(t)
}
r.RawResponse.Reset()
}
// Close method will release Request object and Response object,
// after call Close please don't use these object.
func (r *Response) Close() {
if r.request != nil {
tmp := r.request
r.request = nil
ReleaseRequest(tmp)
}
ReleaseResponse(r)
}
var responsePool = &sync.Pool{
New: func() any {
return &Response{
cookie: []*fasthttp.Cookie{},
RawResponse: fasthttp.AcquireResponse(),
}
},
}
// AcquireResponse returns an empty response object from the pool.
//
// The returned response may be returned to the pool with ReleaseResponse when no longer needed.
// This allows reducing GC load.
func AcquireResponse() *Response {
resp, ok := responsePool.Get().(*Response)
if !ok {
panic("unexpected type from responsePool.Get()")
}
return resp
}
// ReleaseResponse returns the object acquired via AcquireResponse to the pool.
//
// Do not access the released Response object, otherwise data races may occur.
func ReleaseResponse(resp *Response) {
resp.Reset()
responsePool.Put(resp)
}