Skip to main content
  1. Tutorials/

🐜 Print HTTP request/response for debugging in Go

·3 mins

When writing an HTTP server or client in Go, it is often useful to print the full HTTP request or response to the standard output for debugging purposes. Using fmt.Println() may not be sufficient in this case because the output is not formatted and difficult to read. The better idea is to use the httputil.DumpRequest(), httputil.DumpRequestOut() and httputil.DumpResponse() functions which were created to pretty-print the HTTP request and response. Such pretty-printing or dumping means that the request/response is presented in a similar format as it is sent to and received from the server.

The rules for using these functions are simple:

  • Use httputil.DumpRequest() if you want to pretty-print the request on the server side.
  • Use httputil.DumpRequestOut() if you want to dump the request on the client side.
  • Use httputil.DumpResponse() if you want to log the server response.
  • The second argument in each of these functions: body bool is a flag indicating if the body of the request/response should also be returned.

Log client requests and responses #

Check the example below to learn how to dump the HTTP client request and response using the httputil.DumpRequestOut() and httputil.DumpResponse() functions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
    "fmt"
    "log"
    "net/http"
    "net/http/httputil"
)

const serverAddr = "http://example.com/"

func main() {
    req, err := http.NewRequest(http.MethodGet, serverAddr, nil)
    if err != nil {
        log.Fatal(err)
    }
    req.Header.Add("test-header", "test-header-value")

    reqDump, err := httputil.DumpRequestOut(req, true)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("REQUEST:\n%s", string(reqDump))

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        log.Fatal(err)
    }

    respDump, err := httputil.DumpResponse(resp, true)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("RESPONSE:\n%s", string(respDump))
}

Output:

REQUEST:
GET / HTTP/1.1
Host: example.com
User-Agent: Go-http-client/1.1
Test-Header: test-header-value
Accept-Encoding: gzip

RESPONSE:
HTTP/1.1 200 OK
Accept-Ranges: bytes
Age: 357425
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Fri, 25 Feb 2022 06:48:03 GMT
Etag: "3147526947+gzip"
Expires: Fri, 04 Mar 2022 06:48:03 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECS (bsa/EB18)
Vary: Accept-Encoding
X-Cache: HIT

<!doctype html>
<html>
<head>
    <title>Example Domain</title>

    <meta charset="utf-8" />
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style type="text/css">
...

Log requests on the server side #

From the example below, you can find out how to pretty-print an incoming server request using the httputil.DumpRequest() function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
    "fmt"
    "log"
    "net/http"
    "net/http/httputil"
)

const port = 8080

func indexHandler(w http.ResponseWriter, r *http.Request) {
    reqDump, err := httputil.DumpRequest(r, true)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("REQUEST:\n%s", string(reqDump))
    w.Write([]byte("Hello World"))
}

func main() {
    http.HandleFunc("/", indexHandler)
    log.Printf("Starting HTTP server at port: %d\n", port)
    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}

Output:

2022/02/25 07:03:20 Starting HTTP server at port: 8080
REQUEST:
GET / HTTP/1.1
Host: localhost:8080
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,en-US;q=0.8,en;q=0.7
Connection: keep-alive
Dnt: 1
Sec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36

REQUEST:
GET /favicon.ico HTTP/1.1
Host: localhost:8080
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,en-US;q=0.8,en;q=0.7
Connection: keep-alive
Dnt: 1
Referer: http://localhost:8080/
Sec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Sec-Fetch-Dest: image
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36