Commit 765a887d9cd53b76fae22516266a5f4e80aca250

Authored by Marko Tikvić
1 parent 8a81bda58b
Exists in master

io.EOF workaround

Showing 2 changed files with 21 additions and 9 deletions   Show diff stats
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
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