Skip to main content

Handling Responses

The Response struct represents the server’s reply and provides methods to access status codes, headers, body content, and cookies.

Structure

type Response struct {
    client      *Client
    request     *Request
    cookie      []*fasthttp.Cookie
    RawResponse *fasthttp.Response
}

Status Information

Get response status details:

Status Code

Get the numeric HTTP status code:
resp, err := client.Get("https://httpbin.org/get")
if err != nil {
    panic(err)
}

fmt.Printf("Status Code: %d\n", resp.StatusCode())
// Output: Status Code: 200
Signature:
func (r *Response) StatusCode() int

Status Message

Get the HTTP status message:
fmt.Printf("Status: %s\n", resp.Status())
// Output: Status: OK
Signature:
func (r *Response) Status() string

Protocol

Get the HTTP protocol version:
fmt.Printf("Protocol: %s\n", resp.Protocol())
// Output: Protocol: HTTP/1.1
Signature:
func (r *Response) Protocol() string

Headers

Access response headers:

Get Single Header

contentType := resp.Header("Content-Type")
fmt.Printf("Content-Type: %s\n", contentType)
Signature:
func (r *Response) Header(key string) string

Get All Headers

Iterate over all response headers:
import "strings"

for key, values := range resp.Headers() {
    fmt.Printf("%s: %s\n", key, strings.Join(values, ", "))
}
Signature:
func (r *Response) Headers() iter.Seq2[string, []string]

Collect Headers to Map

import "maps"

headers := maps.Collect(resp.Headers())
for key, values := range headers {
    fmt.Printf("%s: %v\n", key, values)
}
Header values are only valid until the response is released. Make copies if you need to store them.

Response Body

Get Body as Bytes

body := resp.Body()
fmt.Printf("Body length: %d bytes\n", len(body))
Signature:
func (r *Response) Body() []byte

Get Body as String

Get the body as a trimmed string:
text := resp.String()
fmt.Println(text)
Signature:
func (r *Response) String() string

Stream Response Body

Read large responses as a stream:
c := client.New()
c.SetStreamResponseBody(true)

resp, err := c.Get("https://httpbin.org/bytes/1024")
if err != nil {
    panic(err)
}
defer resp.Close()

// Read from stream
buf := make([]byte, 256)
total, err := io.CopyBuffer(io.Discard, resp.BodyStream(), buf)
if err != nil {
    panic(err)
}

fmt.Printf("Read %d bytes\n", total)
Signature:
func (r *Response) BodyStream() io.Reader
func (r *Response) IsStreaming() bool
When using BodyStream(), calling Body() afterward may return an empty slice if the stream has been consumed.

Check if Streaming

if resp.IsStreaming() {
    fmt.Println("Response is streaming")
    // Use resp.BodyStream() to read incrementally
} else {
    fmt.Println("Response is buffered")
    // Use resp.Body() for direct access
}

Parse JSON

Unmarshal JSON responses:
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

resp, err := client.Get("https://api.example.com/users/1")
if err != nil {
    panic(err)
}

var user User
if err := resp.JSON(&user); err != nil {
    panic(err)
}

fmt.Printf("User: %s (%s)\n", user.Name, user.Email)
Signature:
func (r *Response) JSON(v any) error

Parse JSON Array

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

resp, err := client.Get("https://api.example.com/users")
if err != nil {
    panic(err)
}

var users []User
if err := resp.JSON(&users); err != nil {
    panic(err)
}

for _, user := range users {
    fmt.Printf("User: %s\n", user.Name)
}

Custom JSON Unmarshaler

Use a custom JSON unmarshaler:
import "github.com/goccy/go-json"

c := client.New()
c.SetJSONUnmarshal(json.Unmarshal)

resp, err := c.Get("https://api.example.com/users/1")
if err != nil {
    panic(err)
}

var user User
if err := resp.JSON(&user); err != nil {
    panic(err)
}

Parse XML

Unmarshal XML responses:
type User struct {
    XMLName xml.Name `xml:"user"`
    ID      int      `xml:"id"`
    Name    string   `xml:"name"`
    Email   string   `xml:"email"`
}

resp, err := client.Get("https://api.example.com/users/1.xml")
if err != nil {
    panic(err)
}

var user User
if err := resp.XML(&user); err != nil {
    panic(err)
}

fmt.Printf("User: %s\n", user.Name)
Signature:
func (r *Response) XML(v any) error

Parse CBOR

Unmarshal CBOR responses:
type User struct {
    ID   int    `cbor:"id"`
    Name string `cbor:"name"`
}

resp, err := client.Get("https://api.example.com/users/1.cbor")
if err != nil {
    panic(err)
}

var user User
if err := resp.CBOR(&user); err != nil {
    panic(err)
}
Signature:
func (r *Response) CBOR(v any) error

Cookies

Access cookies from the response:
resp, err := client.Get("https://httpbin.org/cookies/set/go/fiber")
if err != nil {
    panic(err)
}

cookies := resp.Cookies()
for _, cookie := range cookies {
    fmt.Printf("%s = %s\n", string(cookie.Key()), string(cookie.Value()))
}
// Output: go = fiber
Signature:
func (r *Response) Cookies() []*fasthttp.Cookie
Cookie values are only valid until the response is released. Make copies if you need to store them.

Save Response

Save the response body to a file or writer:

Save to File

resp, err := client.Get("https://httpbin.org/image/png")
if err != nil {
    panic(err)
}
defer resp.Close()

if err := resp.Save("/path/to/image.png"); err != nil {
    panic(err)
}

Save to Writer

var buf bytes.Buffer

resp, err := client.Get("https://httpbin.org/get")
if err != nil {
    panic(err)
}
defer resp.Close()

if err := resp.Save(&buf); err != nil {
    panic(err)
}

fmt.Printf("Saved %d bytes\n", buf.Len())
Signature:
func (r *Response) Save(v any) error
When saving to a file, parent directories are created automatically if they don’t exist.

Error Handling

Check response status and handle errors:
resp, err := client.Get("https://api.example.com/users/999")
if err != nil {
    panic(err)
}
defer resp.Close()

if resp.StatusCode() != 200 {
    fmt.Printf("Error: %s\n", resp.Status())
    fmt.Printf("Body: %s\n", resp.String())
    return
}

var user User
if err := resp.JSON(&user); err != nil {
    fmt.Printf("Failed to parse JSON: %v\n", err)
    return
}

Check Status Code Ranges

switch {
case resp.StatusCode() >= 200 && resp.StatusCode() < 300:
    fmt.Println("Success")
case resp.StatusCode() >= 400 && resp.StatusCode() < 500:
    fmt.Println("Client error")
case resp.StatusCode() >= 500:
    fmt.Println("Server error")
}

Resource Management

Properly release responses to avoid memory leaks:

Close Response

Release both request and response to the pool:
resp, err := client.Get("https://httpbin.org/get")
if err != nil {
    panic(err)
}
defer resp.Close()

// Use the response
fmt.Println(resp.String())
Signature:
func (r *Response) Close()

Manual Pool Management

resp := client.AcquireResponse()
defer client.ReleaseResponse(resp)

// Configure and use response
Signature:
func AcquireResponse() *Response
func ReleaseResponse(resp *Response)
Always call resp.Close() or client.ReleaseResponse(resp) when done with a response. Do not use responses after releasing them.

Access Raw Response

Access the underlying fasthttp response:
resp, err := client.Get("https://httpbin.org/get")
if err != nil {
    panic(err)
}
defer resp.Close()

// Access raw fasthttp response
rawResp := resp.RawResponse
fmt.Printf("Connection close: %v\n", rawResp.Header.ConnectionClose())

Examples

Download and Save File

c := client.New()
c.SetStreamResponseBody(true)

resp, err := c.Get("https://example.com/largefile.zip")
if err != nil {
    panic(err)
}
defer resp.Close()

if err := resp.Save("/downloads/largefile.zip"); err != nil {
    panic(err)
}

fmt.Println("File downloaded successfully")

Parse Nested JSON

type Repository struct {
    Name        string `json:"name"`
    FullName    string `json:"full_name"`
    Description string `json:"description"`
    Owner       struct {
        Login string `json:"login"
    } `json:"owner"`
}

resp, err := client.Get("https://api.github.com/repos/gofiber/fiber")
if err != nil {
    panic(err)
}
defer resp.Close()

var repo Repository
if err := resp.JSON(&repo); err != nil {
    panic(err)
}

fmt.Printf("Repository: %s\n", repo.FullName)
fmt.Printf("Owner: %s\n", repo.Owner.Login)
fmt.Printf("Description: %s\n", repo.Description)

Handle API Errors

type APIError struct {
    Error   string `json:"error"`
    Message string `json:"message"`
}

resp, err := client.Get("https://api.example.com/users/999")
if err != nil {
    panic(err)
}
defer resp.Close()

if resp.StatusCode() != 200 {
    var apiErr APIError
    if err := resp.JSON(&apiErr); err != nil {
        fmt.Printf("Failed to parse error response: %v\n", err)
        return
    }
    fmt.Printf("API Error: %s - %s\n", apiErr.Error, apiErr.Message)
    return
}

var user User
if err := resp.JSON(&user); err != nil {
    panic(err)
}

Next Steps

Request/Response Hooks

Intercept and modify responses

Examples

See complete working examples

Build docs developers (and LLMs) love