github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/commands/http/client.go (about) 1 package http 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "net/url" 11 "reflect" 12 "strconv" 13 "strings" 14 15 cmds "github.com/ipfs/go-ipfs/commands" 16 config "github.com/ipfs/go-ipfs/repo/config" 17 18 context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" 19 ) 20 21 const ( 22 ApiUrlFormat = "http://%s%s/%s?%s" 23 ApiPath = "/api/v0" // TODO: make configurable 24 ) 25 26 // Client is the commands HTTP client interface. 27 type Client interface { 28 Send(req cmds.Request) (cmds.Response, error) 29 } 30 31 type client struct { 32 serverAddress string 33 httpClient http.Client 34 } 35 36 func NewClient(address string) Client { 37 // We cannot use the default transport because of a bug in go's connection reuse 38 // code. It causes random failures in the connection including io.EOF and connection 39 // refused on 'client.Do' 40 return &client{ 41 serverAddress: address, 42 httpClient: http.Client{ 43 Transport: &http.Transport{ 44 DisableKeepAlives: true, 45 }, 46 }, 47 } 48 } 49 50 func (c *client) Send(req cmds.Request) (cmds.Response, error) { 51 52 if req.Context() == nil { 53 log.Warningf("no context set in request") 54 if err := req.SetRootContext(context.TODO()); err != nil { 55 return nil, err 56 } 57 } 58 59 // save user-provided encoding 60 previousUserProvidedEncoding, found, err := req.Option(cmds.EncShort).String() 61 if err != nil { 62 return nil, err 63 } 64 65 // override with json to send to server 66 req.SetOption(cmds.EncShort, cmds.JSON) 67 68 // stream channel output 69 req.SetOption(cmds.ChanOpt, "true") 70 71 query, err := getQuery(req) 72 if err != nil { 73 return nil, err 74 } 75 76 var fileReader *MultiFileReader 77 var reader io.Reader 78 79 if req.Files() != nil { 80 fileReader = NewMultiFileReader(req.Files(), true) 81 reader = fileReader 82 } else { 83 // if we have no file data, use an empty Reader 84 // (http.NewRequest panics when a nil Reader is used) 85 reader = strings.NewReader("") 86 } 87 88 path := strings.Join(req.Path(), "/") 89 url := fmt.Sprintf(ApiUrlFormat, c.serverAddress, ApiPath, path, query) 90 91 httpReq, err := http.NewRequest("POST", url, reader) 92 if err != nil { 93 return nil, err 94 } 95 96 // TODO extract string consts? 97 if fileReader != nil { 98 httpReq.Header.Set(contentTypeHeader, "multipart/form-data; boundary="+fileReader.Boundary()) 99 httpReq.Header.Set(contentDispHeader, "form-data: name=\"files\"") 100 } else { 101 httpReq.Header.Set(contentTypeHeader, applicationOctetStream) 102 } 103 version := config.CurrentVersionNumber 104 httpReq.Header.Set(uaHeader, fmt.Sprintf("/go-ipfs/%s/", version)) 105 106 ec := make(chan error, 1) 107 rc := make(chan cmds.Response, 1) 108 dc := req.Context().Done() 109 110 go func() { 111 httpRes, err := c.httpClient.Do(httpReq) 112 if err != nil { 113 ec <- err 114 return 115 } 116 117 // using the overridden JSON encoding in request 118 res, err := getResponse(httpRes, req) 119 if err != nil { 120 ec <- err 121 return 122 } 123 124 rc <- res 125 }() 126 127 for { 128 select { 129 case <-dc: 130 log.Debug("Context cancelled, cancelling HTTP request...") 131 tr := http.DefaultTransport.(*http.Transport) 132 tr.CancelRequest(httpReq) 133 dc = nil // Wait for ec or rc 134 case err := <-ec: 135 return nil, err 136 case res := <-rc: 137 if found && len(previousUserProvidedEncoding) > 0 { 138 // reset to user provided encoding after sending request 139 // NB: if user has provided an encoding but it is the empty string, 140 // still leave it as JSON. 141 req.SetOption(cmds.EncShort, previousUserProvidedEncoding) 142 } 143 return res, nil 144 } 145 } 146 } 147 148 func getQuery(req cmds.Request) (string, error) { 149 query := url.Values{} 150 for k, v := range req.Options() { 151 str := fmt.Sprintf("%v", v) 152 query.Set(k, str) 153 } 154 155 args := req.Arguments() 156 argDefs := req.Command().Arguments 157 158 argDefIndex := 0 159 160 for _, arg := range args { 161 argDef := argDefs[argDefIndex] 162 // skip ArgFiles 163 for argDef.Type == cmds.ArgFile { 164 argDefIndex++ 165 argDef = argDefs[argDefIndex] 166 } 167 168 query.Add("arg", arg) 169 170 if len(argDefs) > argDefIndex+1 { 171 argDefIndex++ 172 } 173 } 174 175 return query.Encode(), nil 176 } 177 178 // getResponse decodes a http.Response to create a cmds.Response 179 func getResponse(httpRes *http.Response, req cmds.Request) (cmds.Response, error) { 180 var err error 181 res := cmds.NewResponse(req) 182 183 contentType := httpRes.Header.Get(contentTypeHeader) 184 contentType = strings.Split(contentType, ";")[0] 185 186 lengthHeader := httpRes.Header.Get(contentLengthHeader) 187 if len(lengthHeader) > 0 { 188 length, err := strconv.ParseUint(lengthHeader, 10, 64) 189 if err != nil { 190 return nil, err 191 } 192 res.SetLength(length) 193 } 194 195 rr := &httpResponseReader{httpRes} 196 res.SetCloser(rr) 197 198 if contentType != applicationJson { 199 // for all non json output types, just stream back the output 200 res.SetOutput(rr) 201 return res, nil 202 203 } else if len(httpRes.Header.Get(channelHeader)) > 0 { 204 // if output is coming from a channel, decode each chunk 205 outChan := make(chan interface{}) 206 207 go readStreamedJson(req, rr, outChan) 208 209 res.SetOutput((<-chan interface{})(outChan)) 210 return res, nil 211 } 212 213 dec := json.NewDecoder(rr) 214 215 // If we ran into an error 216 if httpRes.StatusCode >= http.StatusBadRequest { 217 e := cmds.Error{} 218 219 switch { 220 case httpRes.StatusCode == http.StatusNotFound: 221 // handle 404s 222 e.Message = "Command not found." 223 e.Code = cmds.ErrClient 224 225 case contentType == plainText: 226 // handle non-marshalled errors 227 mes, err := ioutil.ReadAll(rr) 228 if err != nil { 229 return nil, err 230 } 231 e.Message = string(mes) 232 e.Code = cmds.ErrNormal 233 234 default: 235 // handle marshalled errors 236 err = dec.Decode(&e) 237 if err != nil { 238 return nil, err 239 } 240 } 241 242 res.SetError(e, e.Code) 243 244 return res, nil 245 } 246 247 outputType := reflect.TypeOf(req.Command().Type) 248 v, err := decodeTypedVal(outputType, dec) 249 if err != nil && err != io.EOF { 250 return nil, err 251 } 252 253 res.SetOutput(v) 254 255 return res, nil 256 } 257 258 // read json objects off of the given stream, and write the objects out to 259 // the 'out' channel 260 func readStreamedJson(req cmds.Request, rr io.Reader, out chan<- interface{}) { 261 defer close(out) 262 dec := json.NewDecoder(rr) 263 outputType := reflect.TypeOf(req.Command().Type) 264 265 ctx := req.Context() 266 267 for { 268 v, err := decodeTypedVal(outputType, dec) 269 if err != nil { 270 if err != io.EOF { 271 log.Error(err) 272 } 273 return 274 } 275 276 select { 277 case <-ctx.Done(): 278 return 279 case out <- v: 280 } 281 } 282 } 283 284 // decode a value of the given type, if the type is nil, attempt to decode into 285 // an interface{} anyways 286 func decodeTypedVal(t reflect.Type, dec *json.Decoder) (interface{}, error) { 287 var v interface{} 288 var err error 289 if t != nil { 290 v = reflect.New(t).Interface() 291 err = dec.Decode(v) 292 } else { 293 err = dec.Decode(&v) 294 } 295 296 return v, err 297 } 298 299 // httpResponseReader reads from the response body, and checks for an error 300 // in the http trailer upon EOF, this error if present is returned instead 301 // of the EOF. 302 type httpResponseReader struct { 303 resp *http.Response 304 } 305 306 func (r *httpResponseReader) Read(b []byte) (int, error) { 307 n, err := r.resp.Body.Read(b) 308 309 // reading on a closed response body is as good as an io.EOF here 310 if err != nil && strings.Contains(err.Error(), "read on closed response body") { 311 err = io.EOF 312 } 313 if err == io.EOF { 314 _ = r.resp.Body.Close() 315 trailerErr := r.checkError() 316 if trailerErr != nil { 317 return n, trailerErr 318 } 319 } 320 return n, err 321 } 322 323 func (r *httpResponseReader) checkError() error { 324 if e := r.resp.Trailer.Get(StreamErrHeader); e != "" { 325 return errors.New(e) 326 } 327 return nil 328 } 329 330 func (r *httpResponseReader) Close() error { 331 return r.resp.Body.Close() 332 }