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  }