1
1
package http
2
2
3
3
import (
4
+ "bufio"
4
5
"errors"
5
6
"fmt"
6
7
"io"
@@ -71,6 +72,11 @@ func NewHandler(ctx cmds.Context, root *cmds.Command, allowedOrigin string) *Han
71
72
return & Handler {internal , c .Handler (internal )}
72
73
}
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
+
74
80
func (i internalHandler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
75
81
log .Debug ("Incoming API request: " , r .URL )
76
82
@@ -102,8 +108,8 @@ func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
102
108
// get the node's context to pass into the commands.
103
109
node , err := i .ctx .GetNode ()
104
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 )
107
113
return
108
114
}
109
115
@@ -122,23 +128,32 @@ func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
122
128
sendResponse (w , req , res )
123
129
}
124
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 ) {
128
132
if _ , ok := res .Output ().(io.Reader ); ok {
129
- mime = ""
130
133
// we don't set the Content-Type for streams, so that browsers can MIME-sniff the type themselves
131
134
// we set this header so clients have a way to know this is an output stream
132
135
// (not marshalled command output)
133
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
142
157
}
143
158
144
159
status := 200
@@ -149,7 +164,7 @@ func sendResponse(w http.ResponseWriter, req cmds.Request, res cmds.Response) {
149
164
} else {
150
165
status = http .StatusInternalServerError
151
166
}
152
- // TODO: do we just ignore this error? or what?
167
+ // NOTE: The error will actually be written out by the reader below
153
168
}
154
169
155
170
out , err := res .Reader ()
@@ -158,6 +173,11 @@ func sendResponse(w http.ResponseWriter, req cmds.Request, res cmds.Response) {
158
173
return
159
174
}
160
175
176
+ h := w .Header ()
177
+ if res .Length () > 0 {
178
+ h .Set (contentLengthHeader , strconv .FormatUint (res .Length (), 10 ))
179
+ }
180
+
161
181
// if output is a channel and user requested streaming channels,
162
182
// use chunk copier for the output
163
183
_ , isChan := res .Output ().(chan interface {})
@@ -166,26 +186,30 @@ func sendResponse(w http.ResponseWriter, req cmds.Request, res cmds.Response) {
166
186
}
167
187
168
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 )
172
198
}
199
+ h .Set (streamHeader , "1" )
200
+ h .Set (transferEncodingHeader , "chunked" )
173
201
174
- if err := copyChunks (mime , status , isChan , res . Length () , w , out ); err != nil {
202
+ if err := copyChunks (status , w , out ); err != nil {
175
203
log .Error ("error while writing stream" , err )
176
204
}
177
205
}
178
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
-
184
207
// Copies from an io.Reader to a http.ResponseWriter.
185
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 {
187
210
hijacker , ok := w .(http.Hijacker )
188
211
if ! ok {
212
+ log .Error ("Failed to create hijacker! cannot continue!" )
189
213
return errors .New ("Could not create hijacker" )
190
214
}
191
215
conn , writer , err := hijacker .Hijack ()
@@ -194,51 +218,20 @@ func copyChunks(contentType string, status int, channel bool, length uint64, w h
194
218
}
195
219
defer conn .Close ()
196
220
221
+ // write status
197
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 " )
211
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 )
216
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
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 )
239
232
240
- streamErr := writeChunks ()
241
- writer .WriteString ("0\r \n " ) // close body
233
+ // close body
234
+ writer .WriteString ("0\r \n " )
242
235
243
236
// if there was a stream error, write out an error trailer. hopefully
244
237
// the client will pick it up!
@@ -250,6 +243,34 @@ func copyChunks(contentType string, status int, channel bool, length uint64, w h
250
243
return streamErr
251
244
}
252
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
+
253
274
func sanitizedErrStr (err error ) string {
254
275
s := err .Error ()
255
276
s = strings .Split (s , "\n " )[0 ]
0 commit comments