better refactor of http handler code
License: MIT Signed-off-by: Jeromy <jeromyj@gmail.com>
Jeromy committed
Jul 25, 2015 at 18:46 UTC
2b06ffaa7d42499abda471097c0159ee2faf3d76
1 file changed
+86
-65
commands/http/handler.go
+86
-65
@@ -1,6 +1,7 @@
1
package http
2
3
import (
4
+ "bufio"
5
"errors"
6
"fmt"
7
"io"
@@ -71,6 +72,11 @@ func NewHandler(ctx cmds.Context, root *cmds.Command, allowedOrigin string) *Han
72
return &Handler{internal, c.Handler(internal)}
73
}
74
75
+func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
76
+ // Call the CORS handler which wraps the internal handler.
77
+ i.corsHandler.ServeHTTP(w, r)
78
+}
79
+
80
func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
81
log.Debug("Incoming API request: ", r.URL)
82
@@ -102,8 +108,8 @@ func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
108
// get the node's context to pass into the commands.
109
node, err := i.ctx.GetNode()
110
if err != nil {
105
- err = fmt.Errorf("cmds/http: couldn't GetNode(): %s", err)
106
- http.Error(w, err.Error(), http.StatusInternalServerError)
111
+ s := fmt.Sprintf("cmds/http: couldn't GetNode(): %s", err)
112
+ http.Error(w, s, http.StatusInternalServerError)
113
return
114
}
115
@@ -122,23 +128,32 @@ func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
128
sendResponse(w, req, res)
129
}
130
125
-func sendResponse(w http.ResponseWriter, req cmds.Request, res cmds.Response) {
126
-
127
- var mime string
131
+func guessMimeType(res cmds.Response) (string, error) {
132
if _, ok := res.Output().(io.Reader); ok {
129
- mime = ""
133
// we don't set the Content-Type for streams, so that browsers can MIME-sniff the type themselves
134
// we set this header so clients have a way to know this is an output stream
135
// (not marshalled command output)
136
// TODO: set a specific Content-Type if the command response needs it to be a certain type
134
- } else {
135
- // Try to guess mimeType from the encoding option
136
- enc, found, err := res.Request().Option(cmds.EncShort).String()
137
- if err != nil || !found {
138
- w.WriteHeader(http.StatusInternalServerError)
139
- return
140
- }
141
- mime = mimeTypes[enc]
137
+ return "", nil
138
+ }
139
+
140
+ // Try to guess mimeType from the encoding option
141
+ enc, found, err := res.Request().Option(cmds.EncShort).String()
142
+ if err != nil {
143
+ return "", err
144
+ }
145
+ if !found {
146
+ return "", errors.New("no encoding option set")
147
+ }
148
+
149
+ return mimeTypes[enc], nil
150
+}
151
+
152
+func sendResponse(w http.ResponseWriter, req cmds.Request, res cmds.Response) {
153
+ mime, err := guessMimeType(res)
154
+ if err != nil {
155
+ http.Error(w, err.Error(), http.StatusInternalServerError)
156
+ return
157
}
158
159
status := 200
@@ -149,7 +164,7 @@ func sendResponse(w http.ResponseWriter, req cmds.Request, res cmds.Response) {
164
} else {
165
status = http.StatusInternalServerError
166
}
152
- // TODO: do we just ignore this error? or what?
167
+ // NOTE: The error will actually be written out by the reader below
168
}
169
170
out, err := res.Reader()
@@ -158,6 +173,11 @@ func sendResponse(w http.ResponseWriter, req cmds.Request, res cmds.Response) {
173
return
174
}
175
176
+ h := w.Header()
177
+ if res.Length() > 0 {
178
+ h.Set(contentLengthHeader, strconv.FormatUint(res.Length(), 10))
179
+ }
180
+
181
// if output is a channel and user requested streaming channels,
182
// use chunk copier for the output
183
_, isChan := res.Output().(chan interface{})
@@ -166,26 +186,30 @@ func sendResponse(w http.ResponseWriter, req cmds.Request, res cmds.Response) {
186
}
187
188
streamChans, _, _ := req.Option("stream-channels").Bool()
169
- if isChan && streamChans {
170
- // streaming output from a channel will always be json objects
171
- mime = applicationJson
189
+ if isChan {
190
+ h.Set(channelHeader, "1")
191
+ if streamChans {
192
+ // streaming output from a channel will always be json objects
193
+ mime = applicationJson
194
+ }
195
+ }
196
+ if mime != "" {
197
+ h.Set(contentTypeHeader, mime)
198
}
199
+ h.Set(streamHeader, "1")
200
+ h.Set(transferEncodingHeader, "chunked")
201
174
- if err := copyChunks(mime, status, isChan, res.Length(), w, out); err != nil {
202
+ if err := copyChunks(status, w, out); err != nil {
203
log.Error("error while writing stream", err)
204
}
205
}
206
179
-func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
180
- // Call the CORS handler which wraps the internal handler.
181
- i.corsHandler.ServeHTTP(w, r)
182
-}
183
-
207
// Copies from an io.Reader to a http.ResponseWriter.
208
// Flushes chunks over HTTP stream as they are read (if supported by transport).
186
-func copyChunks(contentType string, status int, channel bool, length uint64, w http.ResponseWriter, out io.Reader) error {
209
+func copyChunks(status int, w http.ResponseWriter, out io.Reader) error {
210
hijacker, ok := w.(http.Hijacker)
211
if !ok {
212
+ log.Error("Failed to create hijacker! cannot continue!")
213
return errors.New("Could not create hijacker")
214
}
215
conn, writer, err := hijacker.Hijack()
@@ -194,51 +218,20 @@ func copyChunks(contentType string, status int, channel bool, length uint64, w h
218
}
219
defer conn.Close()
220
221
+ // write status
222
writer.WriteString(fmt.Sprintf("HTTP/1.1 %d %s\r\n", status, http.StatusText(status)))
198
- writer.WriteString(streamHeader + ": 1\r\n")
199
- if contentType != "" {
200
- writer.WriteString(contentTypeHeader + ": " + contentType + "\r\n")
201
- }
202
- if channel {
203
- writer.WriteString(channelHeader + ": 1\r\n")
204
- }
205
- if length > 0 {
206
- w.Header().Set(contentLengthHeader, strconv.FormatUint(length, 10))
207
- }
208
- writer.WriteString(transferEncodingHeader + ": chunked\r\n")
209
-
210
- writer.WriteString("\r\n")
223
212
- writeChunks := func() error {
213
- buf := make([]byte, 32*1024)
214
- for {
215
- n, err := out.Read(buf)
224
+ // Write out headers
225
+ w.Header().Write(writer)
226
217
- if n > 0 {
218
- length := fmt.Sprintf("%x\r\n", n)
219
- writer.WriteString(length)
220
-
221
- _, err := writer.Write(buf[0:n])
222
- if err != nil {
223
- return err
224
- }
225
-
226
- writer.WriteString("\r\n")
227
- writer.Flush()
228
- }
227
+ // end of headers
228
+ writer.WriteString("\r\n")
229
230
- if err != nil && err != io.EOF {
231
- return err
232
- }
233
- if err == io.EOF {
234
- break
235
- }
236
- }
237
- return nil
238
- }
230
+ // write body
231
+ streamErr := writeChunks(out, writer)
232
240
- streamErr := writeChunks()
241
- writer.WriteString("0\r\n") // close body
233
+ // close body
234
+ writer.WriteString("0\r\n")
235
236
// if there was a stream error, write out an error trailer. hopefully
237
// the client will pick it up!
@@ -250,6 +243,34 @@ func copyChunks(contentType string, status int, channel bool, length uint64, w h
243
return streamErr
244
}
245
246
+func writeChunks(r io.Reader, w *bufio.ReadWriter) error {
247
+ buf := make([]byte, 32*1024)
248
+ for {
249
+ n, err := r.Read(buf)
250
+
251
+ if n > 0 {
252
+ length := fmt.Sprintf("%x\r\n", n)
253
+ w.WriteString(length)
254
+
255
+ _, err := w.Write(buf[0:n])
256
+ if err != nil {
257
+ return err
258
+ }
259
+
260
+ w.WriteString("\r\n")
261
+ w.Flush()
262
+ }
263
+
264
+ if err != nil && err != io.EOF {
265
+ return err
266
+ }
267
+ if err == io.EOF {
268
+ break
269
+ }
270
+ }
271
+ return nil
272
+}
273
+
274
func sanitizedErrStr(err error) string {
275
s := err.Error()
276
s = strings.Split(s, "\n")[0]