-
Notifications
You must be signed in to change notification settings - Fork 0
/
helpers.response.go
177 lines (156 loc) · 5.26 KB
/
helpers.response.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
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"time"
)
// CustomResponseWriter is a wrapper for http.ResponseWriter. It is
// used to record response details like status code and body size.
// The underlying network connection is tracked for dynamic read/write
// deadline setup.
type CustomResponseWriter struct {
http.ResponseWriter
conn net.Conn
code int
bytes int
wrote bool
}
// NewCustomResponseWriter provides CustomResponseWriter with 200 as status code.
func NewCustomResponseWriter(rw http.ResponseWriter, c net.Conn) *CustomResponseWriter {
return &CustomResponseWriter{
ResponseWriter: rw,
conn: c,
code: 200,
}
}
// Header implements http.Header interface.
func (cw *CustomResponseWriter) Header() http.Header {
return cw.ResponseWriter.Header()
}
// WriteHeader implements http.WriteHeader interface.
func (cw *CustomResponseWriter) WriteHeader(code int) {
if cw.Header().Get("X-DRAP-ABORTED") != "" {
cw.code = code
cw.wrote = true
return
}
if !cw.wrote {
cw.code = code
cw.wrote = true
cw.ResponseWriter.WriteHeader(code)
}
}
// Write implements http.Write interface. If the header X-DRAP-ABORTED was set
// that means the timeout middleware was already triggered so the final handler
// should not send any response to client.
func (cw *CustomResponseWriter) Write(bytes []byte) (int, error) {
if cw.Header().Get("X-DRAP-ABORTED") != "" {
return 0, fmt.Errorf("handler: request timed out or cancelled")
}
if !cw.wrote {
cw.WriteHeader(cw.code)
}
n, err := cw.ResponseWriter.Write(bytes)
cw.bytes += n
return n, err
}
// Status returns the written status code.
func (cw *CustomResponseWriter) Status() int {
return cw.code
}
// Bytes returns bytes written as response body.
func (cw *CustomResponseWriter) Bytes() int {
return cw.bytes
}
// Unwrap returns native response writer and used by
// the http.ResponseController during its operation.
func (cw *CustomResponseWriter) Unwrap() http.ResponseWriter {
return cw.ResponseWriter
}
// SetWriteDeadline rewrites the underlying connection write deadline.
// This is called by http.ResponseController SetWriteDeadline method.
func (cw *CustomResponseWriter) SetWriteDeadline(t time.Time) error {
return cw.conn.SetWriteDeadline(t)
}
// SetReadDeadline rewrites the underlying connection read deadline.
// This is called by http.ResponseController SetReadDeadline method.
func (cw *CustomResponseWriter) SetReadDeadline(t time.Time) error {
return cw.conn.SetReadDeadline(t)
}
// APIError is the data model sent when an error occurred during request processing.
type APIError struct {
RequestID string `json:"requestid"`
Status int `json:"status"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
// APIResponse is the data model sent when a request succeed.
// We use the omitempty flag on the `total` field. This helps
// set the value for `GetAllBook` calls only.
type APIResponse struct {
RequestID string `json:"requestid"`
Status int `json:"status"`
Message string `json:"message"`
Total *int `json:"total,omitempty"`
Data interface{} `json:"data"`
}
func NewAPIError(requestid string, status int, message string, data interface{}) *APIError {
return &APIError{
RequestID: requestid,
Status: status,
Message: message,
Data: data,
}
}
func GenericResponse(requestid string, status int, message string, total *int, data interface{}) *APIResponse {
return &APIResponse{
RequestID: requestid,
Status: status,
Message: message,
Total: total,
Data: data,
}
}
// WriteErrorResponse is used to send error response to client. In case the client closes the request,
// it logs the stats with the Nginx non standard status code 499 (Client Closed Request). This means
// the timeout middleware already kicked-in and did send the response. In case of request processing
// timeout we set the status code to 504 which will be used to log the stats. Here also, the middleware
// already kicked-in and sent a json message to client.
func WriteErrorResponse(ctx context.Context, w http.ResponseWriter, errResp *APIError) error {
if err := ctx.Err(); err != nil {
if errors.Is(err, context.DeadlineExceeded) {
w.WriteHeader(http.StatusGatewayTimeout)
} else {
w.WriteHeader(499)
}
return ctx.Err()
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(errResp.Status)
return json.NewEncoder(w).Encode(errResp)
}
// WriteResponse is used to send success api response to client. It sets the status code to 499
// in case client cancelled the request, and to 504 if the request processing timed out.
func WriteResponse(ctx context.Context, w http.ResponseWriter, resp *APIResponse) error {
if err := ctx.Err(); err != nil {
if errors.Is(err, context.DeadlineExceeded) {
w.WriteHeader(http.StatusGatewayTimeout)
} else {
w.WriteHeader(499)
}
return ctx.Err()
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(resp.Status)
return json.NewEncoder(w).Encode(resp)
}
// StatusResponse is the data model sent when status endpoint is called.
type StatusResponse struct {
RequestID string `json:"requestid"`
Status string `json:"status"`
Message string `json:"message"`
}