-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhttpmiddleware.go
186 lines (167 loc) · 7.34 KB
/
httpmiddleware.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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package golog
import (
"bytes"
"context"
"net/http"
"net/http/httptest"
"strings"
)
const HTTPNoHeaders = "HTTPNoHeaders"
// GetOrCreateRequestUUID gets a UUID from a http.Request or creates one.
// The X-Request-ID or X-Correlation-ID HTTP request headers will be
// parsed as UUID in the format "994d5800-afca-401f-9c2f-d9e3e106e9ef".
// If the request has no properly formatted ID,
// then a random v4 UUID will be returned.
func GetOrCreateRequestUUID(request *http.Request) [16]byte {
xRequestID := request.Header.Get("X-Request-ID")
if xRequestID == "" {
xRequestID = request.Header.Get("X-Correlation-ID")
}
requestID, err := ParseUUID(xRequestID)
if err != nil {
return NewUUID()
}
return requestID
}
// GetOrCreateRequestID gets a string from a http.Request or creates
// one as formatted random UUID.
// The X-Request-ID or X-Correlation-ID HTTP request header values
// will be returned if available,
// else a random v4 UUID will be returned formatted as string.
func GetOrCreateRequestID(request *http.Request) string {
requestID := request.Header.Get("X-Request-ID")
if requestID != "" {
return requestID
}
requestID = request.Header.Get("X-Correlation-ID")
if requestID != "" {
return requestID
}
return FormatUUID(NewUUID())
}
// GetRequestUUIDFromContext returns a UUID that was added
// to the context as UUID attribute with the key "requestID".
// If the context has no requestID attribute
// then false will be returned for ok.
func GetRequestUUIDFromContext(ctx context.Context) (requestID [16]byte, ok bool) {
attrib, ok := AttribsFromContext(ctx).Get("requestID").(UUID)
if !ok {
return [16]byte{}, false
}
return attrib.Val, true
}
// GetRequestIDFromContext returns a string
// that was added to the context as attribute with the key "requestID".
// If the context has no requestID attribute
// then and empty string will be returned.
func GetRequestIDFromContext(ctx context.Context) string {
requestID := AttribsFromContext(ctx).Get("requestID")
if requestID == nil {
return ""
}
return requestID.GetValString()
}
// GetOrCreateRequestUUIDFromContext returns a UUID that was added
// to the context as UUID attribute with the key "requestID"
// If the context has no requestID attribute
// then a new random v4 UUID will be returned.
func GetOrCreateRequestUUIDFromContext(ctx context.Context) [16]byte {
requestID, ok := AttribsFromContext(ctx).Get("requestID").(UUID)
if !ok {
return NewUUID()
}
return requestID.Val
}
// ContextWithRequestUUID adds the passed requestID as UUID
// attribute with the key "requestID" to the context.
func ContextWithRequestUUID(ctx context.Context, requestID [16]byte) context.Context {
return ContextWithAttribs(ctx, UUID{Key: "requestID", Val: requestID})
}
// ContextWithRequestID adds the passed requestID as string
// attribute with the key "requestID" to the context.
func ContextWithRequestID(ctx context.Context, requestID string) context.Context {
return ContextWithAttribs(ctx, String{Key: "requestID", Val: requestID})
}
// HTTPMiddlewareHandler returns a HTTP middleware handler that passes through a UUID requestID.
// The requestID will be added as UUID Attrib to the http.Request before calling the next handler.
// If available the X-Request-ID or X-Correlation-ID HTTP request header will be used as requestID.
// It has to be a valid UUID in the format "994d5800-afca-401f-9c2f-d9e3e106e9ef".
// If the request has no requestID, then a random v4 UUID will be used.
// The requestID will also be set at the http.ResponseWriter as X-Request-ID header
// before calling the next handler, which has a chance to change it.
// If onlyHeaders are passed then only those headers are logged if available,
// or pass HTTPNoHeaders to disable header logging.
// To disable logging of the request at all and just pass through
// the requestID pass LevelInvalid as log level.
// See also HTTPMiddlewareFunc.
func HTTPMiddlewareHandler(next http.Handler, logger *Logger, level Level, message string, onlyHeaders ...string) http.Handler {
return http.HandlerFunc(
func(response http.ResponseWriter, request *http.Request) {
requestID := GetOrCreateRequestUUID(request)
response.Header().Set("X-Request-ID", FormatUUID(requestID))
requestWithID := RequestWithAttribs(request, UUID{Key: "requestID", Val: requestID})
logger.NewMessage(request.Context(), level, message).
Request(requestWithID, onlyHeaders...).
Log()
next.ServeHTTP(response, requestWithID)
},
)
}
// HTTPMiddlewareFunc returns a HTTP middleware function that passes through a UUID requestID.
// The requestID will be added as UUID Attrib to the http.Request before calling the next handler.
// If available the X-Request-ID or X-Correlation-ID HTTP request header will be used as requestID.
// It has to be a valid UUID in the format "994d5800-afca-401f-9c2f-d9e3e106e9ef".
// If the request has no requestID, then a random v4 UUID will be used.
// The requestID will also be set at the http.ResponseWriter as X-Request-ID header
// before calling the next handler, which has a chance to change it.
// If onlyHeaders are passed then only those headers are logged if available,
// or pass HTTPNoHeaders to disable header logging.
// To disable logging of the request at all and just pass through
// the requestID pass LevelInvalid as log level.
// Compatible with github.com/gorilla/mux.MiddlewareFunc.
// See also HTTPMiddlewareHandler.
func HTTPMiddlewareFunc(logger *Logger, level Level, message string, onlyHeaders ...string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return HTTPMiddlewareHandler(next, logger, level, message, onlyHeaders...)
}
}
// HTTPMiddlewareRespondPlaintextCtxLogsIfNotOK adds a TextWriterConfig to the request context
// that collects all log messages that are created with the passed through context
// and writes an alternative plaintext response with the collected logs
// if the response status code from the wrapped handler is not 200 OK.
// In case of a status code 200 OK response from the wrapped handler
// the original response will be passed through.
func HTTPMiddlewareRespondPlaintextCtxLogsIfNotOK(wrapped http.Handler, filter ...LevelFilter) http.HandlerFunc {
return func(response http.ResponseWriter, request *http.Request) {
var (
logBuffer = bytes.NewBuffer(nil)
logContext = ContextWithAdditionalWriterConfigs(
request.Context(),
NewTextWriterConfig(logBuffer, nil, nil, filter...),
)
responseRecorder = httptest.NewRecorder()
)
wrapped.ServeHTTP(responseRecorder, request.WithContext(logContext))
if responseRecorder.Code != http.StatusOK {
// In case of an error respond with the recorded logs
response.Header().Set("Content-Type", "text/plain; charset=utf-8")
response.Header().Set("X-Content-Type-Options", "nosniff")
response.Write(logBuffer.Bytes())
// Also respond with the recorded response body if it is text
ct := responseRecorder.Header().Get("Content-Type")
if strings.HasPrefix(ct, "text/") || strings.HasPrefix(ct, "application/json") || strings.HasPrefix(ct, "application/xml") {
response.Write([]byte("\n\n"))
response.Write(responseRecorder.Body.Bytes())
return
}
}
// Response was OK, copy recorded response
// to actual to response
for k, vals := range responseRecorder.Header() {
for _, v := range vals {
response.Header().Add(k, v)
}
}
response.Write(responseRecorder.Body.Bytes())
}
}