forked from goadesign/goa
/
debug.go
135 lines (121 loc) · 3.54 KB
/
debug.go
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package middleware
import (
"bufio"
"bytes"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"sort"
"strings"
goahttp "goa.design/goa/http"
"goa.design/goa/middleware"
)
// responseDupper tees the response to a buffer and a response writer.
type responseDupper struct {
http.ResponseWriter
Buffer *bytes.Buffer
Status int
}
// Debug returns a debug middleware which prints detailed information about
// incoming requests and outgoing responses including all headers, parameters
// and bodies.
func Debug(mux goahttp.Muxer, w io.Writer) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
buf := &bytes.Buffer{}
// Request ID
reqID := r.Context().Value(middleware.RequestIDKey)
if reqID == nil {
reqID = shortID()
}
// Request URL
buf.WriteString(fmt.Sprintf("> [%s] %s %s", reqID, r.Method, r.URL.String()))
// Request Headers
keys := make([]string, len(r.Header))
i := 0
for k := range r.Header {
keys[i] = k
i++
}
sort.Strings(keys)
for _, k := range keys {
buf.WriteString(fmt.Sprintf("\n> [%s] %s: %s", reqID, k, strings.Join(r.Header[k], ", ")))
}
// Request parameters
params := mux.Vars(r)
keys = make([]string, len(params))
i = 0
for k := range params {
keys[i] = k
i++
}
sort.Strings(keys)
for _, k := range keys {
buf.WriteString(fmt.Sprintf("\n> [%s] %s: %s", reqID, k, params[k]))
}
// Request body
b, err := ioutil.ReadAll(r.Body)
if err != nil {
b = []byte("failed to read body: " + err.Error())
}
if len(b) > 0 {
buf.WriteByte('\n')
lines := strings.Split(string(b), "\n")
for _, line := range lines {
buf.WriteString(fmt.Sprintf("[%s] %s\n", reqID, line))
}
}
r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
dupper := &responseDupper{ResponseWriter: rw, Buffer: &bytes.Buffer{}}
h.ServeHTTP(dupper, r)
buf.WriteString(fmt.Sprintf("\n< [%s] %s", reqID, http.StatusText(dupper.Status)))
keys = make([]string, len(dupper.Header()))
i = 0
for k := range dupper.Header() {
keys[i] = k
i++
}
sort.Strings(keys)
for _, k := range keys {
buf.WriteString(fmt.Sprintf("\n< [%s] %s: %s", reqID, k, strings.Join(dupper.Header()[k], ", ")))
}
if dupper.Buffer.Len() > 0 {
buf.WriteByte('\n')
lines := strings.Split(dupper.Buffer.String(), "\n")
for _, line := range lines {
buf.WriteString(fmt.Sprintf("[%s] %s\n", reqID, line))
}
}
buf.WriteByte('\n')
w.Write(buf.Bytes())
})
}
}
// Write writes the data to the buffer and connection as part of an HTTP reply.
func (r *responseDupper) Write(b []byte) (int, error) {
r.Buffer.Write(b)
return r.ResponseWriter.Write(b)
}
// WriteHeader records the status and sends an HTTP response header with status code.
func (r *responseDupper) WriteHeader(s int) {
r.Status = s
r.ResponseWriter.WriteHeader(s)
}
// Hijack supports the http.Hijacker interface.
func (r *responseDupper) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hijacker, ok := r.ResponseWriter.(http.Hijacker); ok {
return hijacker.Hijack()
}
return nil, nil, fmt.Errorf("debug middleware: inner ResponseWriter cannot be hijacked: %T", r.ResponseWriter)
}
// shortID produces a " unique" 6 bytes long string.
// Do not use as a reliable way to get unique IDs, instead use for things like logging.
func shortID() string {
b := make([]byte, 6)
io.ReadFull(rand.Reader, b)
return base64.RawURLEncoding.EncodeToString(b)
}