-
Notifications
You must be signed in to change notification settings - Fork 1
/
handlers.go
208 lines (162 loc) · 5.84 KB
/
handlers.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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package main
import (
"context"
"encoding/json"
"errors"
"net/http"
"time"
"github.com/Dynom/ERI/validator"
"github.com/Dynom/ERI/cmd/web/erihttp/handlers"
"github.com/Dynom/ERI/cmd/web/services"
"github.com/Dynom/ERI/cmd/web/erihttp"
"github.com/sirupsen/logrus"
)
const (
failedRequestError = "Request failed, unable to parse request body. Expected JSON."
domainLookupFailedError = "Request failed, unable to lookup by domain."
failedResponseError = "Generating response failed."
)
type marshalFn func(v interface{}) ([]byte, error)
func NewAutoCompleteHandler(logger logrus.FieldLogger, svc *services.AutocompleteSvc, maxSuggestions uint64, maxBodySize uint64, jsonMarshaller marshalFn) http.HandlerFunc {
if jsonMarshaller == nil {
jsonMarshaller = json.Marshal
}
logger = logger.WithField("handler", "auto complete")
return func(w http.ResponseWriter, r *http.Request) {
var err error
var req erihttp.AutoCompleteRequest
logger := logger.WithField(handlers.RequestID.String(), r.Context().Value(handlers.RequestID))
defer deferClose(r.Body, logger)
body, err := erihttp.GetBodyFromHTTPRequest(r, int64(maxBodySize))
if err != nil {
logger.WithFields(logrus.Fields{
"error": err,
"content_length": r.ContentLength,
}).Errorf("Error handling request %s", err)
w.WriteHeader(http.StatusBadRequest)
// err is expected to be safe to expose to the client
writeErrorJSONResponse(logger, w, &erihttp.AutoCompleteResponse{Error: err.Error()})
return
}
err = json.Unmarshal(body, &req)
if err != nil {
logger.WithError(err).Errorf("Error handling request body %s", err)
w.WriteHeader(http.StatusBadRequest)
writeErrorJSONResponse(logger, w, &erihttp.AutoCompleteResponse{Error: failedRequestError})
return
}
ctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*500)
defer cancel()
if req.Domain == "" {
logger.Debug("Empty argument")
w.WriteHeader(http.StatusBadRequest)
writeErrorJSONResponse(logger, w, &erihttp.AutoCompleteResponse{Error: domainLookupFailedError})
return
}
result, err := svc.Autocomplete(ctx, req.Domain, maxSuggestions)
if err != nil {
logger.WithError(err).Warn("Error during lookup")
if err != ctx.Err() {
// When the context is canceled, we're not going to consider it a bad request
w.WriteHeader(http.StatusBadRequest)
}
// @todo is this a safe error to leak?
writeErrorJSONResponse(logger, w, &erihttp.AutoCompleteResponse{Error: err.Error()})
return
}
response, err := jsonMarshaller(erihttp.AutoCompleteResponse{
Suggestions: result.Suggestions,
})
if err != nil {
logger.WithFields(logrus.Fields{
"response": response,
"error": err,
}).Error("Failed to marshal the response")
w.WriteHeader(http.StatusInternalServerError)
writeErrorJSONResponse(logger, w, &erihttp.AutoCompleteResponse{Error: failedResponseError})
return
}
logger.WithFields(logrus.Fields{
"suggestions": len(result.Suggestions),
"input": req.Domain,
}).Debugf("Autocomplete result")
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(response)
}
}
// NewSuggestHandler constructs an HTTP handler that deals with suggestion requests
func NewSuggestHandler(logger logrus.FieldLogger, svc *services.SuggestSvc, maxBodySize uint64, jsonMarshaller marshalFn) http.HandlerFunc {
if jsonMarshaller == nil {
jsonMarshaller = json.Marshal
}
log := logger.WithField("handler", "suggest")
return func(w http.ResponseWriter, r *http.Request) {
var err error
var req erihttp.SuggestRequest
log := log.WithField(handlers.RequestID.String(), r.Context().Value(handlers.RequestID))
defer deferClose(r.Body, log)
body, err := erihttp.GetBodyFromHTTPRequest(r, int64(maxBodySize))
if err != nil {
log.WithError(err).Error("Error handling request")
w.WriteHeader(http.StatusBadRequest)
writeErrorJSONResponse(logger, w, &erihttp.SuggestResponse{Error: err.Error()})
return
}
err = json.Unmarshal(body, &req)
if err != nil {
log.WithError(err).Error("Error handling request body")
w.WriteHeader(http.StatusBadRequest)
writeErrorJSONResponse(logger, w, &erihttp.SuggestResponse{Error: failedRequestError})
return
}
var alts = []string{req.Email}
result, sugErr := svc.Suggest(r.Context(), req.Email)
if len(result.Alternatives) > 0 {
alts = append(alts[0:0], result.Alternatives...)
}
sr := erihttp.SuggestResponse{
Alternatives: alts,
MalformedSyntax: errors.Is(sugErr, validator.ErrEmailAddressSyntax),
MisconfiguredMX: !result.HasValidMX,
}
if sugErr != nil {
log.WithFields(logrus.Fields{
"suggest_response": sr,
"error": sugErr,
"input": req.Email,
}).Warn("Suggest error")
sr.Error = sugErr.Error()
}
response, err := jsonMarshaller(sr)
if err != nil {
log.WithFields(logrus.Fields{
"response": response,
"error": err,
}).Error("Failed to marshal the response")
w.WriteHeader(http.StatusInternalServerError)
writeErrorJSONResponse(logger, w, &erihttp.SuggestResponse{Error: failedResponseError})
return
}
log.WithFields(logrus.Fields{
"alternatives": alts,
"target": req.Email,
}).Debugf("Done performing check")
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(response)
}
}
func NewHealthHandler(logger logrus.FieldLogger) http.HandlerFunc {
var ok = []byte("OK")
logger = logger.WithField("handler", "health")
return func(w http.ResponseWriter, r *http.Request) {
logger := logger.WithField(handlers.RequestID.String(), r.Context().Value(handlers.RequestID))
w.Header().Set("content-type", "text/plain")
w.WriteHeader(http.StatusOK)
_, err := w.Write(ok)
if err != nil {
logger.WithError(err).Error("failed to write to http.ResponseWriter")
}
}
}