This repository has been archived by the owner on Apr 23, 2018. It is now read-only.
forked from gravitational/trace
-
Notifications
You must be signed in to change notification settings - Fork 0
/
httplib.go
129 lines (120 loc) · 3.31 KB
/
httplib.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
package trace
import (
"encoding/json"
"fmt"
"net/http"
)
// WriteError sets up HTTP error response and writes it to writer w
func WriteError(w http.ResponseWriter, err error) {
if IsAggregate(err) {
for i := 0; i < maxHops; i++ {
var aggErr Aggregate
var ok bool
if aggErr, ok = Unwrap(err).(Aggregate); !ok {
break
}
errors := aggErr.Errors()
if len(errors) == 0 {
break
}
err = errors[0]
}
}
writeError(w, err)
}
// ErrorToCode returns an appropriate HTTP status code based on the provided error type
func ErrorToCode(err error) int {
if IsNotFound(err) {
return http.StatusNotFound
} else if IsBadParameter(err) || IsOAuth2(err) {
return http.StatusBadRequest
} else if IsCompareFailed(err) {
return http.StatusPreconditionFailed
} else if IsAccessDenied(err) {
return http.StatusForbidden
} else if IsAlreadyExists(err) {
return http.StatusConflict
} else if IsLimitExceeded(err) {
return http.StatusTooManyRequests
} else if IsConnectionProblem(err) {
return http.StatusGatewayTimeout
}
return http.StatusInternalServerError
}
func writeError(w http.ResponseWriter, err error) {
replyJSON(w, ErrorToCode(err), err)
}
// ReadError converts http error to internal error type
// based on HTTP response code and HTTP body contents
// if status code does not indicate error, it will return nil
func ReadError(statusCode int, re []byte) error {
var e error
switch statusCode {
case http.StatusNotFound:
e = &NotFoundError{Message: string(re)}
case http.StatusBadRequest:
e = &BadParameterError{Message: string(re)}
case http.StatusPreconditionFailed:
e = &CompareFailedError{Message: string(re)}
case http.StatusForbidden:
e = &AccessDeniedError{Message: string(re)}
case http.StatusConflict:
e = &AlreadyExistsError{Message: string(re)}
case http.StatusTooManyRequests:
e = &LimitExceededError{Message: string(re)}
case http.StatusGatewayTimeout:
e = &ConnectionProblemError{Message: string(re)}
default:
if statusCode < 200 || statusCode > 299 {
return Errorf(string(re))
}
return nil
}
return unmarshalError(e, re)
}
func replyJSON(w http.ResponseWriter, code int, err error) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
var out []byte
if IsDebug() {
// trace error can marshal itself,
// otherwise capture error message and marshal it explicitly
var obj interface{} = err
if _, ok := err.(*TraceErr); !ok {
obj = message{Message: err.Error()}
}
out, err = json.MarshalIndent(obj, "", " ")
if err != nil {
out = []byte(fmt.Sprintf(`{"message": "internal marshal error: %v"}`, err))
}
} else {
innerError := err
if terr, ok := err.(Error); ok {
innerError = terr.OrigError()
}
out, err = json.Marshal(message{Message: innerError.Error()})
}
w.Write(out)
}
type message struct {
Message string `json:"message"`
}
func unmarshalError(err error, responseBody []byte) error {
if len(responseBody) == 0 {
return err
}
var raw RawTrace
if err2 := json.Unmarshal(responseBody, &raw); err2 != nil {
return err
}
if len(raw.Traces) != 0 && len(raw.Err) != 0 {
// try to capture traces, if there are any
err2 := json.Unmarshal(raw.Err, err)
if err2 != nil {
return err
}
return &TraceErr{Traces: raw.Traces, Err: err, Message: raw.Message}
}
json.Unmarshal(responseBody, err)
return err
}