Commit 765a887d9cd53b76fae22516266a5f4e80aca250
1 parent
8a81bda58b
Exists in
master
io.EOF workaround
Showing
2 changed files
with
21 additions
and
9 deletions
Show diff stats
http.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "encoding/json" | 4 | "encoding/json" |
5 | "fmt" | 5 | "fmt" |
6 | "io" | ||
7 | "net/http" | 6 | "net/http" |
8 | ) | 7 | ) |
9 | 8 | ||
10 | // StatusRecorder ... | 9 | // StatusRecorder ... |
11 | type StatusRecorder struct { | 10 | type StatusRecorder struct { |
12 | writer http.ResponseWriter | 11 | writer http.ResponseWriter |
13 | status int | 12 | status int |
14 | size int | 13 | size int |
15 | } | 14 | } |
16 | 15 | ||
17 | // NewStatusRecorder ... | 16 | // NewStatusRecorder ... |
18 | func NewStatusRecorder(w http.ResponseWriter) *StatusRecorder { | 17 | func NewStatusRecorder(w http.ResponseWriter) *StatusRecorder { |
19 | return &StatusRecorder{ | 18 | return &StatusRecorder{ |
20 | writer: w, | 19 | writer: w, |
21 | status: 0, | 20 | status: 0, |
22 | size: 0, | 21 | size: 0, |
23 | } | 22 | } |
24 | } | 23 | } |
25 | 24 | ||
26 | // WriteHeader is a wrapper http.ResponseWriter interface | 25 | // WriteHeader is a wrapper http.ResponseWriter interface |
27 | func (r *StatusRecorder) WriteHeader(code int) { | 26 | func (r *StatusRecorder) WriteHeader(code int) { |
28 | r.status = code | 27 | r.status = code |
29 | r.writer.WriteHeader(code) | 28 | r.writer.WriteHeader(code) |
30 | } | 29 | } |
31 | 30 | ||
32 | // Write is a wrapper for http.ResponseWriter interface | 31 | // Write is a wrapper for http.ResponseWriter interface |
33 | func (r *StatusRecorder) Write(in []byte) (int, error) { | 32 | func (r *StatusRecorder) Write(in []byte) (int, error) { |
34 | r.size = len(in) | 33 | r.size = len(in) |
35 | return r.writer.Write(in) | 34 | return r.writer.Write(in) |
36 | } | 35 | } |
37 | 36 | ||
38 | // Header is a wrapper for http.ResponseWriter interface | 37 | // Header is a wrapper for http.ResponseWriter interface |
39 | func (r *StatusRecorder) Header() http.Header { | 38 | func (r *StatusRecorder) Header() http.Header { |
40 | return r.writer.Header() | 39 | return r.writer.Header() |
41 | } | 40 | } |
42 | 41 | ||
43 | // Status ... | 42 | // Status ... |
44 | func (r *StatusRecorder) Status() int { | 43 | func (r *StatusRecorder) Status() int { |
45 | return r.status | 44 | return r.status |
46 | } | 45 | } |
47 | 46 | ||
48 | // Size ... | 47 | // Size ... |
49 | func (r *StatusRecorder) Size() int { | 48 | func (r *StatusRecorder) Size() int { |
50 | return r.size | 49 | return r.size |
51 | } | 50 | } |
52 | 51 | ||
53 | // NotFoundHandlerFunc writes HTTP error 404 to w. | 52 | // NotFoundHandlerFunc writes HTTP error 404 to w. |
54 | func NotFoundHandlerFunc(w http.ResponseWriter, req *http.Request) { | 53 | func NotFoundHandlerFunc(w http.ResponseWriter, req *http.Request) { |
55 | SetDefaultHeaders(w) | 54 | SetDefaultHeaders(w) |
56 | if req.Method == "OPTIONS" { | 55 | if req.Method == "OPTIONS" { |
57 | return | 56 | return |
58 | } | 57 | } |
59 | NotFound(w, req, fmt.Sprintf("Resource you requested was not found: %s", req.URL.String())) | 58 | NotFound(w, req, fmt.Sprintf("Resource you requested was not found: %s", req.URL.String())) |
60 | } | 59 | } |
61 | 60 | ||
62 | // SetContentType ... | 61 | // SetContentType ... |
63 | func SetContentType(w http.ResponseWriter, ctype string) { | 62 | func SetContentType(w http.ResponseWriter, ctype string) { |
64 | w.Header().Set("Content-Type", ctype) | 63 | w.Header().Set("Content-Type", ctype) |
65 | } | 64 | } |
66 | 65 | ||
67 | // SetResponseStatus ... | 66 | // SetResponseStatus ... |
68 | func SetResponseStatus(w http.ResponseWriter, status int) { | 67 | func SetResponseStatus(w http.ResponseWriter, status int) { |
69 | w.WriteHeader(status) | 68 | w.WriteHeader(status) |
70 | } | 69 | } |
71 | 70 | ||
72 | // WriteResponse ... | 71 | // WriteResponse ... |
73 | func WriteResponse(w http.ResponseWriter, content []byte) { | 72 | func WriteResponse(w http.ResponseWriter, content []byte) { |
74 | w.Write(content) | 73 | w.Write(content) |
75 | } | 74 | } |
76 | 75 | ||
77 | // SetDefaultHeaders set's default headers for an HTTP response. | 76 | // SetDefaultHeaders set's default headers for an HTTP response. |
78 | func SetDefaultHeaders(w http.ResponseWriter) { | 77 | func SetDefaultHeaders(w http.ResponseWriter) { |
79 | w.Header().Set("Access-Control-Allow-Origin", "*") | 78 | w.Header().Set("Access-Control-Allow-Origin", "*") |
80 | w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") | 79 | w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") |
81 | w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") | 80 | w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") |
82 | SetContentType(w, "application/json; charset=utf-8") | 81 | SetContentType(w, "application/json; charset=utf-8") |
83 | } | 82 | } |
84 | 83 | ||
85 | // GetLocale ... | 84 | // GetLocale ... |
86 | func GetLocale(req *http.Request, dflt string) string { | 85 | func GetLocale(req *http.Request, dflt string) string { |
87 | loc := req.FormValue("locale") | 86 | loc := req.FormValue("locale") |
88 | if loc == "" { | 87 | if loc == "" { |
89 | return dflt | 88 | return dflt |
90 | } | 89 | } |
91 | return loc | 90 | return loc |
92 | } | 91 | } |
93 | 92 | ||
94 | // Success ... | 93 | // Success ... |
95 | func Success(w http.ResponseWriter, payload interface{}, code int) { | 94 | func Success(w http.ResponseWriter, payload interface{}, code int) { |
96 | w.WriteHeader(code) | 95 | w.WriteHeader(code) |
97 | if payload != nil { | 96 | if payload != nil { |
98 | json.NewEncoder(w).Encode(payload) | 97 | json.NewEncoder(w).Encode(payload) |
99 | } | 98 | } |
100 | } | 99 | } |
101 | 100 | ||
102 | // OK ... | 101 | // OK ... |
103 | func OK(w http.ResponseWriter, payload interface{}) { | 102 | func OK(w http.ResponseWriter, payload interface{}) { |
104 | Success(w, payload, http.StatusOK) | 103 | Success(w, payload, http.StatusOK) |
105 | } | 104 | } |
106 | 105 | ||
107 | // Created ... | 106 | // Created ... |
108 | func Created(w http.ResponseWriter, payload interface{}) { | 107 | func Created(w http.ResponseWriter, payload interface{}) { |
109 | Success(w, payload, http.StatusCreated) | 108 | Success(w, payload, http.StatusCreated) |
110 | } | 109 | } |
111 | 110 | ||
112 | type weberror struct { | 111 | type weberror struct { |
113 | Request string `json:"request"` | 112 | Request string `json:"request"` |
114 | Error string `json:"error"` | 113 | Error string `json:"error"` |
115 | } | 114 | } |
116 | 115 | ||
117 | // Error ... | 116 | // Error ... |
118 | func Error(w http.ResponseWriter, r *http.Request, code int, err string) { | 117 | func Error(w http.ResponseWriter, r *http.Request, code int, err string) { |
119 | werr := weberror{Error: err, Request: r.Method + " " + r.RequestURI} | 118 | werr := weberror{Error: err, Request: r.Method + " " + r.RequestURI} |
120 | w.WriteHeader(code) | 119 | w.WriteHeader(code) |
121 | json.NewEncoder(w).Encode(werr) | 120 | json.NewEncoder(w).Encode(werr) |
122 | } | 121 | } |
123 | 122 | ||
124 | // BadRequest ... | 123 | // BadRequest ... |
125 | func BadRequest(w http.ResponseWriter, r *http.Request, err string) { | 124 | func BadRequest(w http.ResponseWriter, r *http.Request, err string) { |
126 | Error(w, r, http.StatusBadRequest, err) | 125 | Error(w, r, http.StatusBadRequest, err) |
127 | } | 126 | } |
128 | 127 | ||
129 | // Unauthorized ... | 128 | // Unauthorized ... |
130 | func Unauthorized(w http.ResponseWriter, r *http.Request, err string) { | 129 | func Unauthorized(w http.ResponseWriter, r *http.Request, err string) { |
131 | Error(w, r, http.StatusUnauthorized, err) | 130 | Error(w, r, http.StatusUnauthorized, err) |
132 | } | 131 | } |
133 | 132 | ||
134 | // Forbidden ... | 133 | // Forbidden ... |
135 | func Forbidden(w http.ResponseWriter, r *http.Request, err string) { | 134 | func Forbidden(w http.ResponseWriter, r *http.Request, err string) { |
136 | Error(w, r, http.StatusForbidden, err) | 135 | Error(w, r, http.StatusForbidden, err) |
137 | } | 136 | } |
138 | 137 | ||
139 | // NotFound ... | 138 | // NotFound ... |
140 | func NotFound(w http.ResponseWriter, r *http.Request, err string) { | 139 | func NotFound(w http.ResponseWriter, r *http.Request, err string) { |
141 | Error(w, r, http.StatusNotFound, err) | 140 | Error(w, r, http.StatusNotFound, err) |
142 | } | 141 | } |
143 | 142 | ||
144 | // Conflict ... | 143 | // Conflict ... |
145 | func Conflict(w http.ResponseWriter, r *http.Request, err string) { | 144 | func Conflict(w http.ResponseWriter, r *http.Request, err string) { |
146 | Error(w, r, http.StatusConflict, err) | 145 | Error(w, r, http.StatusConflict, err) |
147 | } | 146 | } |
148 | 147 | ||
149 | // InternalServerError ... | 148 | // InternalServerError ... |
150 | func InternalServerError(w http.ResponseWriter, r *http.Request, err string) { | 149 | func InternalServerError(w http.ResponseWriter, r *http.Request, err string) { |
151 | Error(w, r, http.StatusInternalServerError, err) | 150 | Error(w, r, http.StatusInternalServerError, err) |
152 | } | 151 | } |
153 | |||
154 | // DecodeJSON decodes JSON data from r to v. | ||
155 | // Returns an error if it fails. | ||
156 | func DecodeJSON(r io.Reader, v interface{}) error { | ||
157 | return json.NewDecoder(r).Decode(v) | ||
158 | } | ||
159 | 152 |
json.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | // TODO(marko): If DecodeJSON() returns io.EOF treat it as if there is no response body, since response content length can sometimes be -1. | ||
4 | |||
3 | import ( | 5 | import ( |
4 | "bytes" | 6 | "bytes" |
5 | "encoding/json" | 7 | "encoding/json" |
8 | "io" | ||
6 | "net/http" | 9 | "net/http" |
7 | "net/url" | 10 | "net/url" |
8 | ) | 11 | ) |
9 | 12 | ||
13 | // DecodeJSON decodes JSON data from r to v. | ||
14 | // Returns an error if it fails. | ||
15 | func DecodeJSON(r io.Reader, v interface{}) error { | ||
16 | return json.NewDecoder(r).Decode(v) | ||
17 | } | ||
18 | |||
10 | func GetJSON(url string, v interface{}, params url.Values, headers http.Header) (status int, err error) { | 19 | func GetJSON(url string, v interface{}, params url.Values, headers http.Header) (status int, err error) { |
11 | p := params.Encode() | 20 | p := params.Encode() |
12 | if p != "" { | 21 | if p != "" { |
13 | url += "?" + p | 22 | url += "?" + p |
14 | } | 23 | } |
15 | 24 | ||
16 | req, err := http.NewRequest(http.MethodGet, url, nil) | 25 | req, err := http.NewRequest(http.MethodGet, url, nil) |
17 | if err != nil { | 26 | if err != nil { |
18 | return 0, err | 27 | return 0, err |
19 | } | 28 | } |
20 | 29 | ||
21 | if headers != nil { | 30 | if headers != nil { |
22 | for k, head := range headers { | 31 | for k, head := range headers { |
23 | for i, h := range head { | 32 | for i, h := range head { |
24 | if i == 0 { | 33 | if i == 0 { |
25 | req.Header.Set(k, h) | 34 | req.Header.Set(k, h) |
26 | } else { | 35 | } else { |
27 | req.Header.Add(k, h) | 36 | req.Header.Add(k, h) |
28 | } | 37 | } |
29 | } | 38 | } |
30 | } | 39 | } |
31 | } | 40 | } |
32 | 41 | ||
33 | resp, err := http.DefaultClient.Do(req) | 42 | resp, err := http.DefaultClient.Do(req) |
34 | if err != nil { | 43 | if err != nil { |
35 | return 0, err | 44 | return 0, err |
36 | } | 45 | } |
46 | defer resp.Body.Close() | ||
37 | status = resp.StatusCode | 47 | status = resp.StatusCode |
38 | 48 | ||
39 | return status, DecodeJSON(resp.Body, v) | 49 | if err = DecodeJSON(resp.Body, v); err == io.EOF { |
50 | err = nil | ||
51 | } | ||
52 | |||
53 | return status, err | ||
40 | } | 54 | } |
41 | 55 | ||
42 | func PostJSON(url string, v, r interface{}, params url.Values, headers http.Header) (status int, err error) { | 56 | func PostJSON(url string, v, r interface{}, params url.Values, headers http.Header) (status int, err error) { |
43 | buffer := bytes.NewBuffer(make([]byte, 0)) | 57 | buffer := bytes.NewBuffer(make([]byte, 0)) |
44 | json.NewEncoder(buffer).Encode(v) | 58 | json.NewEncoder(buffer).Encode(v) |
45 | 59 | ||
46 | p := params.Encode() | 60 | p := params.Encode() |
47 | if p != "" { | 61 | if p != "" { |
48 | url += "?" + p | 62 | url += "?" + p |
49 | } | 63 | } |
50 | 64 | ||
51 | req, err := http.NewRequest(http.MethodPost, url, buffer) | 65 | req, err := http.NewRequest(http.MethodPost, url, buffer) |
52 | if err != nil { | 66 | if err != nil { |
53 | return 0, err | 67 | return 0, err |
54 | } | 68 | } |
55 | 69 | ||
56 | if headers != nil { | 70 | if headers != nil { |
57 | for k, head := range headers { | 71 | for k, head := range headers { |
58 | for i, h := range head { | 72 | for i, h := range head { |
59 | if i == 0 { | 73 | if i == 0 { |
60 | req.Header.Set(k, h) | 74 | req.Header.Set(k, h) |
61 | } else { | 75 | } else { |
62 | req.Header.Add(k, h) | 76 | req.Header.Add(k, h) |
63 | } | 77 | } |
64 | } | 78 | } |
65 | } | 79 | } |
66 | } | 80 | } |
67 | req.Header.Set("Content-Type", "application/json") | 81 | req.Header.Set("Content-Type", "application/json") |
68 | 82 | ||
69 | resp, err := http.DefaultClient.Do(req) | 83 | resp, err := http.DefaultClient.Do(req) |
70 | if err != nil { | 84 | if err != nil { |
71 | return 0, err | 85 | return 0, err |
72 | } | 86 | } |
73 | status = resp.StatusCode | 87 | status = resp.StatusCode |
88 | defer resp.Body.Close() | ||
89 | |||
90 | if err = DecodeJSON(resp.Body, v); err == io.EOF { | ||
91 | err = nil | ||
92 | } | ||
74 | 93 | ||
75 | return status, DecodeJSON(resp.Body, r) | 94 | return status, err |
76 | } | 95 | } |
77 | 96 |